diff --git a/.githooks/pre-commit b/.githooks/pre-commit index 5b5c33b522..ebc6116a21 100755 --- a/.githooks/pre-commit +++ b/.githooks/pre-commit @@ -12,4 +12,3 @@ if [ ! -z "$files" ]; then make fmt git add $(echo "$files" | paste -s -d " " -) fi - diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/parachain-ci.yml similarity index 99% rename from .github/workflows/build-and-test.yml rename to .github/workflows/parachain-ci.yml index a25f9afa8c..20ac29b00f 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/parachain-ci.yml @@ -1,4 +1,4 @@ -name: Build & Test +name: Parachain CI on: push: @@ -51,6 +51,7 @@ jobs: - 'pallets/**' - 'primitives/**' - 'runtime/**' + - 'mock-tee-primitives/**' - 'docker/Dockerfile' - '**/Cargo.lock' - '**/Cargo.toml' diff --git a/.github/workflows/tee-worker-ci.yml b/.github/workflows/tee-worker-ci.yml new file mode 100644 index 0000000000..adb00242ce --- /dev/null +++ b/.github/workflows/tee-worker-ci.yml @@ -0,0 +1,412 @@ +name: Tee-worker CI + +# this is a modified version of tee-worker/.github/workflows/build_and_test.yml with +# extra triggering control. +# +# the original file (`tee-worker/.github/workflows/build_and_test.yml`) is kept to sync +# upstream changes, therefore we need to manually apply the changes to this file. + +# tried symbolic link -- didn't work, see https://stackoverflow.com/a/71704019 + +on: + workflow_dispatch: + push: + branches: + - tee-dev + pull_request: + branches: + - tee-dev + +env: + CARGO_TERM_COLOR: always + LOG_DIR: logs + BUILD_CONTAINER_NAME: integritee_worker_enclave_test + UPLOAD_DOWNLOAD_DIR_PREFIX: /tmp + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +# to minimise the changes by setting a default working directory +# please note it only applies to the `run` command, not `use` command +defaults: + run: + working-directory: tee-worker + +jobs: + check-file-change: + runs-on: ubuntu-latest + # see https://github.com/orgs/community/discussions/25722 + if: ${{ github.event_name == 'push' || !github.event.pull_request.draft }} + outputs: + src: ${{ steps.filter.outputs.src }} + steps: + - uses: actions/checkout@v3 + with: + fetch-depth: 0 + + # Checks to see if any files in the PR/commit match one of the listed file types. + # We can use this filter to decide whether or not to build docker images + - uses: dorny/paths-filter@v2 + id: filter + with: + filters: | + src: + - 'tee-worker/**' + + build-parachain-docker: + runs-on: ubuntu-latest + needs: check-file-change + steps: + - uses: actions/checkout@v3 + + - name: Build docker image + run: | + ./scripts/litentry/build_parachain_docker.sh + + - name: Save docker image + run: | + docker save litentry/litentry-parachain:tee-dev -o ${{ env.UPLOAD_DOWNLOAD_DIR_PREFIX }}/litentry-parachain.tar + + - name: Upload docker image + uses: actions/upload-artifact@v3 + with: + name: parachain-artifact + path: ${{ env.UPLOAD_DOWNLOAD_DIR_PREFIX }}/litentry-parachain.tar + + build-test: + runs-on: ubuntu-20.04 + needs: check-file-change + strategy: + fail-fast: false + matrix: + include: + - flavor_id: sidechain + mode: sidechain + - flavor_id: offchain-worker + mode: offchain-worker + - flavor_id: teeracle + mode: teeracle + - flavor_id: sidechain-evm + mode: sidechain + additional_features: evm + - flavor_id: mockserver + mode: sidechain + additional_features: mockserver + + steps: + - uses: actions/checkout@v3 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v2 + with: + buildkitd-flags: --debug + driver: docker-container + + - name: Build Worker & Run Cargo Test + env: + DOCKER_BUILDKIT: 1 + run: > + docker build -t integritee-worker-${{ matrix.flavor_id }}-${{ github.sha }} + --target deployed-worker + --build-arg WORKER_MODE_ARG=${{ matrix.mode }} --build-arg ADDITIONAL_FEATURES_ARG=${{ matrix.additional_features }} + -f build.Dockerfile . + + - name: Build CLI client + env: + DOCKER_BUILDKIT: 1 + run: > + docker build -t integritee-cli-client-${{ matrix.flavor_id }}-${{ github.sha }} + --target deployed-client + --build-arg WORKER_MODE_ARG=${{ matrix.mode }} --build-arg ADDITIONAL_FEATURES_ARG=${{ matrix.additional_features }} + -f build.Dockerfile . + + - run: docker images --all + + - name: Test Enclave # cargo test is not supported in the enclave, see: https://github.com/apache/incubator-teaclave-sgx-sdk/issues/232 + run: docker run --name ${{ env.BUILD_CONTAINER_NAME }} integritee-worker-${{ matrix.flavor_id }}-${{ github.sha }} test --all + + - name: Export worker image(s) + run: | + docker image save integritee-worker-${{ matrix.flavor_id }}-${{ github.sha }} | gzip > ${{ env.UPLOAD_DOWNLOAD_DIR_PREFIX }}/integritee-worker-${{ matrix.flavor_id }}-${{ github.sha }}.tar.gz + docker image save integritee-cli-client-${{ matrix.flavor_id }}-${{ github.sha }} | gzip > ${{ env.UPLOAD_DOWNLOAD_DIR_PREFIX }}/integritee-cli-client-${{ matrix.flavor_id }}-${{ github.sha }}.tar.gz + + - name: Upload worker image + uses: actions/upload-artifact@v3 + with: + name: integritee-worker-${{ matrix.flavor_id }}-${{ github.sha }}.tar.gz + path: ${{ env.UPLOAD_DOWNLOAD_DIR_PREFIX }}/integritee-worker-${{ matrix.flavor_id }}-${{ github.sha }}.tar.gz + + - name: Upload CLI client image + uses: actions/upload-artifact@v3 + with: + name: integritee-cli-client-${{ matrix.flavor_id }}-${{ github.sha }}.tar.gz + path: ${{ env.UPLOAD_DOWNLOAD_DIR_PREFIX }}/integritee-cli-client-${{ matrix.flavor_id }}-${{ github.sha }}.tar.gz + + clippy: + runs-on: ubuntu-latest + needs: check-file-change + container: "integritee/integritee-dev:0.1.9" + steps: + - uses: actions/checkout@v3 + - name: init rust + # enclave is not in the same workspace + run: rustup show && cd enclave-runtime && rustup show + + - name: Clippy default features + run: cargo clippy -- -D warnings + - name: Enclave # Enclave is separate as it's not in the workspace + run: cd enclave-runtime && cargo clippy -- -D warnings + + - name: Clippy with EVM feature + run: | + cargo clippy --features evm -- -D warnings + cd enclave-runtime && cargo clippy --features evm -- -D warnings + - name: Clippy with Sidechain feature + run: | + cargo clippy --features sidechain -- -D warnings + cd enclave-runtime && cargo clippy --features sidechain -- -D warnings + - name: Clippy with Teeracle feature + run: | + cargo clippy --features teeracle -- -D warnings + cd enclave-runtime && cargo clippy --features teeracle -- -D warnings + - name: Clippy with Offchain-worker feature + run: | + cargo clippy --features offchain-worker -- -D warnings + cd enclave-runtime && cargo clippy --features offchain-worker -- -D warnings + - name: Fail-fast; cancel other jobs + if: failure() + uses: andymckay/cancel-action@0.2 + + fmt: + runs-on: ubuntu-latest + needs: check-file-change + steps: + - uses: actions/checkout@v3 + - name: init rust + run: rustup show + + - name: Worker & Client + run: cargo fmt --all -- --check + - name: Enclave # Enclave is separate as it's not in the workspace + run: cd enclave-runtime && cargo fmt --all -- --check + + - name: Install taplo + run: cargo install taplo-cli --locked + - name: Cargo.toml fmt + run: taplo fmt --check + + - name: Fail-fast; cancel other jobs + if: failure() + uses: andymckay/cancel-action@0.2 + + integration-tests: + runs-on: ubuntu-20.04 + needs: + - build-parachain-docker + - build-test + env: + WORKER_IMAGE_TAG: integritee-worker:dev + CLIENT_IMAGE_TAG: integritee-cli:dev + COINMARKETCAP_KEY: ${{ secrets.COINMARKETCAP_KEY }} + TEERACLE_INTERVAL_SECONDS: 4 + + strategy: + fail-fast: false + matrix: + include: + - test: M6 + flavor_id: sidechain + demo_name: demo-indirect-invocation + - test: M8 + flavor_id: sidechain + demo_name: demo-direct-call + - test: Sidechain + flavor_id: sidechain + demo_name: demo-sidechain + - test: M6 + flavor_id: offchain-worker + demo_name: demo-indirect-invocation + - test: Teeracle + flavor_id: teeracle + demo_name: demo-teeracle + - test: Benchmark + flavor_id: sidechain + demo_name: sidechain-benchmark + - test: EVM + flavor_id: sidechain-evm + demo_name: demo-smart-contract + # Litentry + - test: user-shielding-key + flavor_id: sidechain + demo_name: user-shielding-key + - test: ts-tests + flavor_id: mockserver + demo_name: ts-tests + + steps: + - uses: actions/checkout@v3 + + - name: Pull polkadot image + run: | + docker pull parity/polkadot:latest + + - uses: actions/download-artifact@v3 + with: + name: parachain-artifact + path: ${{ env.UPLOAD_DOWNLOAD_DIR_PREFIX }} + + - name: Load docker image + run: | + docker load -i ${{ env.UPLOAD_DOWNLOAD_DIR_PREFIX }}/litentry-parachain.tar + + - name: Download Worker Image + uses: actions/download-artifact@v3 + with: + name: integritee-worker-${{ matrix.flavor_id }}-${{ github.sha }}.tar.gz + path: ${{ env.UPLOAD_DOWNLOAD_DIR_PREFIX }} + + - name: Download CLI client Image + uses: actions/download-artifact@v3 + with: + name: integritee-cli-client-${{ matrix.flavor_id }}-${{ github.sha }}.tar.gz + path: ${{ env.UPLOAD_DOWNLOAD_DIR_PREFIX }} + + - name: Load Worker & Client Images + env: + DOCKER_BUILDKIT: 1 + run: | + docker image load --input ${{ env.UPLOAD_DOWNLOAD_DIR_PREFIX }}/integritee-worker-${{ matrix.flavor_id }}-${{ github.sha }}.tar.gz + docker image load --input ${{ env.UPLOAD_DOWNLOAD_DIR_PREFIX }}/integritee-cli-client-${{ matrix.flavor_id }}-${{ github.sha }}.tar.gz + docker images --all + + - name: Re-name Image Tags + run: | + docker tag integritee-worker-${{ matrix.flavor_id }}-${{ github.sha }} ${{ env.WORKER_IMAGE_TAG }} + docker tag integritee-cli-client-${{ matrix.flavor_id }}-${{ github.sha }} ${{ env.CLIENT_IMAGE_TAG }} + docker images --all + + - name: Generate parachain artefacts + run: | + ./scripts/litentry/generate_parachain_artefacts.sh + + - name: Build litentry parachain docker images + run: | + cd docker + docker-compose -f litentry-parachain.build.yml build + + - name: Integration Test ${{ matrix.test }}-${{ matrix.flavor_id }} + timeout-minutes: 30 + run: | + cd docker + docker-compose -f docker-compose.yml -f ${{ matrix.demo_name }}.yml up --no-build --exit-code-from ${{ matrix.demo_name }} -- ${{ matrix.demo_name }} + + - name: Stop docker containers + run: | + cd docker + docker compose -f docker-compose.yml -f ${{ matrix.demo_name }}.yml stop + + - name: Collect Docker Logs + continue-on-error: true + if: always() + uses: jwalton/gh-docker-logs@v2 + with: + #images: '${{ env.WORKER_IMAGE_TAG }},${{ env.CLIENT_IMAGE_TAG }}' + tail: all + dest: ${{ env.UPLOAD_DOWNLOAD_DIR_PREFIX }}/${{ env.LOG_DIR }} + + - name: Upload logs + if: always() + uses: actions/upload-artifact@v3 + with: + name: logs-${{ matrix.test }}-${{ matrix.flavor_id }} + path: ${{ env.UPLOAD_DOWNLOAD_DIR_PREFIX }}/${{ env.LOG_DIR }} + + # Only push docker image when tests are passed on dev branch + push-docker-image: + runs-on: ubuntu-latest + needs: + - integration-tests + if: ${{ success() && (github.event_name == 'push') && (github.ref == 'refs/heads/dev') }} + steps: + - uses: actions/download-artifact@v3 + with: + name: parachain-artifact + path: ${{ env.UPLOAD_DOWNLOAD_DIR_PREFIX }} + + - name: Load docker image + run: | + docker load -i ${{ env.UPLOAD_DOWNLOAD_DIR_PREFIX }}/litentry-parachain.tar + + - name: Dockerhub login + uses: docker/login-action@v1 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_PASSWORD }} + + - name: Push docker image + run: docker push litentry/litentry-parachain:tee-dev + + release: + name: Draft Release + if: startsWith(github.ref, 'refs/tags/') + runs-on: ubuntu-latest + needs: [build-test, integration-tests] + outputs: + release_url: ${{ steps.create-release.outputs.html_url }} + asset_upload_url: ${{ steps.create-release.outputs.upload_url }} + steps: + - uses: actions/checkout@v3 + + - name: Download Integritee Service + uses: actions/download-artifact@v3 + with: + name: integritee-worker-sidechain-${{ github.sha }} + path: ${{ env.UPLOAD_DOWNLOAD_DIR_PREFIX }}/integritee-worker-tmp + + - name: Download Integritee Client + uses: actions/download-artifact@v3 + with: + name: integritee-client-sidechain-${{ github.sha }} + path: ${{ env.UPLOAD_DOWNLOAD_DIR_PREFIX }}/integritee-client-tmp + + - name: Download Enclave Signed + uses: actions/download-artifact@v3 + with: + name: enclave-signed-sidechain-${{ github.sha }} + path: ${{ env.UPLOAD_DOWNLOAD_DIR_PREFIX }}/enclave-signed-tmp + + - name: Move service binaries + run: mv ${{ env.UPLOAD_DOWNLOAD_DIR_PREFIX }}/integritee-worker-tmp/integritee-service ./integritee-demo-validateer + + - name: Move service client binaries + run: mv ${{ env.UPLOAD_DOWNLOAD_DIR_PREFIX }}/integritee-client-tmp/integritee-cli ./integritee-client + + - name: Move service client binaries + run: mv ${{ env.UPLOAD_DOWNLOAD_DIR_PREFIX }}/enclave-signed-tmp/enclave.signed.so ./enclave.signed.so + + - name: Create required package.json + run: test -f package.json || echo '{}' >package.json + + - name: Changelog + uses: scottbrenner/generate-changelog-action@master + id: Changelog + + - name: Display structure of downloaded files + run: ls -R + working-directory: . + + - name: Release + id: create-release + uses: softprops/action-gh-release@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + body: | + ${{ steps.Changelog.outputs.changelog }} + draft: true + # note the path change + files: | + tee-worker/integritee-client + tee-worker/integritee-demo-validateer + tee-worker/enclave.signed.so diff --git a/.taplo.toml b/.taplo.toml index a7bd3663bb..ff847e7ac1 100644 --- a/.taplo.toml +++ b/.taplo.toml @@ -1,4 +1,5 @@ include = ["**/Cargo.toml"] +exclude = ["tee-worker/**/Cargo.toml"] [formatting] align_entries = false diff --git a/Makefile b/Makefile index 89df77e619..344463e3a0 100644 --- a/Makefile +++ b/Makefile @@ -188,6 +188,7 @@ taplocheck: fmt: cargo fmt --all taplo fmt + cd tee-worker && make fmt && taplo fmt .PHONY: githooks ## install the githooks githooks: diff --git a/rustfmt.toml b/rustfmt.toml index f4d7ad637a..26f1f75d6d 100644 --- a/rustfmt.toml +++ b/rustfmt.toml @@ -1,3 +1,5 @@ +ignore = ["tee-worker/"] + # Basic hard_tabs = true max_width = 100 diff --git a/tee-worker/.dockerignore b/tee-worker/.dockerignore new file mode 100644 index 0000000000..f82d6e8b32 --- /dev/null +++ b/tee-worker/.dockerignore @@ -0,0 +1,13 @@ +.git +.githooks +.github +.idea +ci/ +docker/ +docs/ +local-setup/ +scripts/ +target/ +tmp/ +*.Dockerfile +Dockerfile \ No newline at end of file diff --git a/tee-worker/.editorconfig b/tee-worker/.editorconfig new file mode 100644 index 0000000000..de2a30a350 --- /dev/null +++ b/tee-worker/.editorconfig @@ -0,0 +1,27 @@ +root = true + +[*] +indent_style = tab +indent_size = 4 +tab_width = 4 +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +max_line_length = 100 +insert_final_newline = true + +[*.yml] +indent_style = space +indent_size = 4 +tab_width = 4 +end_of_line = lf + +[*.ts] +indent_style = space +indent_size = 4 +tab_width = 4 +end_of_line = lf + + +[*.toml] +indent_style = space \ No newline at end of file diff --git a/tee-worker/.gitattributes.orig b/tee-worker/.gitattributes.orig new file mode 100644 index 0000000000..00c1715114 --- /dev/null +++ b/tee-worker/.gitattributes.orig @@ -0,0 +1,18 @@ +# TODO: why do we need binary mode for Cargo.lock? +# Cargo.lock linguist-generated=true -diff + +[attr]rust text eol=lf whitespace=tab-in-indent,trailing-space,tabwidth=4 + +* text=auto eol=lf +*.cpp rust +*.h rust +*.rs rust +*.fixed linguist-language=Rust +src/etc/installer/gfx/* binary +*.woff binary +src/vendor/** -text +Cargo.lock -merge linguist-generated=false + +# Older git versions try to fix line endings on images, this prevents it. +*.png binary +*.ico binary diff --git a/tee-worker/.githooks/README.md b/tee-worker/.githooks/README.md new file mode 100644 index 0000000000..7f19a69309 --- /dev/null +++ b/tee-worker/.githooks/README.md @@ -0,0 +1,8 @@ +To use the hooks under this folder, either set the git config for this repo: +``` +git config core.hooksPath .githooks +``` + +or +manually copy the hook files to .git/hooks/ and double check the files are exectuable. + diff --git a/tee-worker/.githooks/pre-commit b/tee-worker/.githooks/pre-commit new file mode 100755 index 0000000000..5ba0511181 --- /dev/null +++ b/tee-worker/.githooks/pre-commit @@ -0,0 +1,15 @@ +#!/usr/bin/env bash + +set -e + +ROOTDIR=$(git rev-parse --show-toplevel) +cd "$ROOTDIR" + +files=$((git diff --cached --name-only --diff-filter=ACMR | grep -Ei "\.rs$|Cargo.toml$") || true) +if [ ! -z "$files" ]; then + echo "[cargo fmt] and [git add] the following files:" + echo "$files" + taplo fmt + make fmt + git add $(echo "$files" | paste -s -d " " -) +fi \ No newline at end of file diff --git a/tee-worker/.github/workflows/build_and_test.yml b/tee-worker/.github/workflows/build_and_test.yml new file mode 100644 index 0000000000..39788e2af5 --- /dev/null +++ b/tee-worker/.github/workflows/build_and_test.yml @@ -0,0 +1,360 @@ +name: Build, Test, Clippy + +on: + workflow_dispatch: + push: + branches: [ dev ] + tags: + - '[0-9]+.[0-9]+.[0-9]+' + pull_request: + branches: [ dev ] + +env: + CARGO_TERM_COLOR: always + LOG_DIR: logs + BUILD_CONTAINER_NAME: integritee_worker_enclave_test + +jobs: + cancel_previous_runs: + name: Cancel Previous Runs + runs-on: ubuntu-20.04 + steps: + - uses: styfle/cancel-workflow-action@0.11.0 + with: + access_token: ${{ secrets.GITHUB_TOKEN }} + + build-parachain-docker: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + + - name: Build docker image + run: | + ./scripts/litentry/build_parachain_docker.sh + - name: Save docker image + run: | + docker save litentry/litentry-parachain:tee-dev -o litentry-parachain.tar + - name: Upload docker image + uses: actions/upload-artifact@v3 + with: + name: parachain-artifact + path: litentry-parachain.tar + + build-test: + runs-on: ubuntu-20.04 + strategy: + fail-fast: false + matrix: + include: + - flavor_id: sidechain + mode: sidechain + - flavor_id: offchain-worker + mode: offchain-worker + - flavor_id: teeracle + mode: teeracle + - flavor_id: sidechain-evm + mode: sidechain + additional_features: evm + - flavor_id: mockserver + mode: sidechain + additional_features: mockserver + + steps: + - uses: actions/checkout@v3 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v2 + with: + buildkitd-flags: --debug + driver: docker-container + + - name: Build Worker & Run Cargo Test + env: + DOCKER_BUILDKIT: 1 + run: > + docker build -t integritee-worker-${{ matrix.flavor_id }}-${{ github.sha }} + --target deployed-worker + --build-arg WORKER_MODE_ARG=${{ matrix.mode }} --build-arg ADDITIONAL_FEATURES_ARG=${{ matrix.additional_features }} + -f build.Dockerfile . + - name: Build CLI client + env: + DOCKER_BUILDKIT: 1 + run: > + docker build -t integritee-cli-client-${{ matrix.flavor_id }}-${{ github.sha }} + --target deployed-client + --build-arg WORKER_MODE_ARG=${{ matrix.mode }} --build-arg ADDITIONAL_FEATURES_ARG=${{ matrix.additional_features }} + -f build.Dockerfile . + - run: docker images --all + + - name: Test Enclave # cargo test is not supported in the enclave, see: https://github.com/apache/incubator-teaclave-sgx-sdk/issues/232 + run: docker run --name ${{ env.BUILD_CONTAINER_NAME }} integritee-worker-${{ matrix.flavor_id }}-${{ github.sha }} test --all + + - name: Export worker image(s) + run: | + docker image save integritee-worker-${{ matrix.flavor_id }}-${{ github.sha }} | gzip > integritee-worker-${{ matrix.flavor_id }}-${{ github.sha }}.tar.gz + docker image save integritee-cli-client-${{ matrix.flavor_id }}-${{ github.sha }} | gzip > integritee-cli-client-${{ matrix.flavor_id }}-${{ github.sha }}.tar.gz + - name: Upload worker image + uses: actions/upload-artifact@v3 + with: + name: integritee-worker-${{ matrix.flavor_id }}-${{ github.sha }}.tar.gz + path: integritee-worker-${{ matrix.flavor_id }}-${{ github.sha }}.tar.gz + + - name: Upload CLI client image + uses: actions/upload-artifact@v3 + with: + name: integritee-cli-client-${{ matrix.flavor_id }}-${{ github.sha }}.tar.gz + path: integritee-cli-client-${{ matrix.flavor_id }}-${{ github.sha }}.tar.gz + + clippy: + runs-on: ubuntu-latest + container: "integritee/integritee-dev:0.1.9" + steps: + - uses: actions/checkout@v3 + - name: init rust + # enclave is not in the same workspace + run: rustup show && cd enclave-runtime && rustup show + + - name: Clippy default features + run: cargo clippy -- -D warnings + - name: Enclave # Enclave is separate as it's not in the workspace + run: cd enclave-runtime && cargo clippy -- -D warnings + + - name: Clippy with EVM feature + run: | + cargo clippy --features evm -- -D warnings + cd enclave-runtime && cargo clippy --features evm -- -D warnings + - name: Clippy with Sidechain feature + run: | + cargo clippy --features sidechain -- -D warnings + cd enclave-runtime && cargo clippy --features sidechain -- -D warnings + - name: Clippy with Teeracle feature + run: | + cargo clippy --features teeracle -- -D warnings + cd enclave-runtime && cargo clippy --features teeracle -- -D warnings + - name: Clippy with Offchain-worker feature + run: | + cargo clippy --features offchain-worker -- -D warnings + cd enclave-runtime && cargo clippy --features offchain-worker -- -D warnings + - name: Fail-fast; cancel other jobs + if: failure() + uses: andymckay/cancel-action@0.2 + + fmt: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - name: init rust + run: rustup show + + - name: Worker & Client + run: cargo fmt --all -- --check + - name: Enclave # Enclave is separate as it's not in the workspace + run: cd enclave-runtime && cargo fmt --all -- --check + + - name: Install taplo + run: cargo install taplo-cli --locked + - name: Cargo.toml fmt + run: taplo fmt --check + + - name: Fail-fast; cancel other jobs + if: failure() + uses: andymckay/cancel-action@0.2 + + integration-tests: + runs-on: ubuntu-20.04 + needs: + - build-parachain-docker + - build-test + env: + WORKER_IMAGE_TAG: integritee-worker:dev + CLIENT_IMAGE_TAG: integritee-cli:dev + COINMARKETCAP_KEY: ${{ secrets.COINMARKETCAP_KEY }} + TEERACLE_INTERVAL_SECONDS: 4 + + strategy: + fail-fast: false + matrix: + include: + - test: M6 + flavor_id: sidechain + demo_name: demo-indirect-invocation + - test: M8 + flavor_id: sidechain + demo_name: demo-direct-call + - test: Sidechain + flavor_id: sidechain + demo_name: demo-sidechain + - test: M6 + flavor_id: offchain-worker + demo_name: demo-indirect-invocation + - test: Teeracle + flavor_id: teeracle + demo_name: demo-teeracle + - test: Benchmark + flavor_id: sidechain + demo_name: sidechain-benchmark + - test: EVM + flavor_id: sidechain-evm + demo_name: demo-smart-contract + # Litentry + - test: user-shielding-key + flavor_id: sidechain + demo_name: user-shielding-key + - test: ts-tests + flavor_id: mockserver + demo_name: ts-tests + + steps: + - uses: actions/checkout@v3 + + - name: Pull polkadot image + run: | + docker pull parity/polkadot:latest + - uses: actions/download-artifact@v3 + with: + name: parachain-artifact + + - name: Load docker image + run: | + docker load -i litentry-parachain.tar + - name: Download Worker Image + uses: actions/download-artifact@v3 + with: + name: integritee-worker-${{ matrix.flavor_id }}-${{ github.sha }}.tar.gz + path: . + + - name: Download CLI client Image + uses: actions/download-artifact@v3 + with: + name: integritee-cli-client-${{ matrix.flavor_id }}-${{ github.sha }}.tar.gz + path: . + + - name: Load Worker & Client Images + env: + DOCKER_BUILDKIT: 1 + run: | + docker image load --input integritee-worker-${{ matrix.flavor_id }}-${{ github.sha }}.tar.gz + docker image load --input integritee-cli-client-${{ matrix.flavor_id }}-${{ github.sha }}.tar.gz + docker images --all + - name: Re-name Image Tags + run: | + docker tag integritee-worker-${{ matrix.flavor_id }}-${{ github.sha }} ${{ env.WORKER_IMAGE_TAG }} + docker tag integritee-cli-client-${{ matrix.flavor_id }}-${{ github.sha }} ${{ env.CLIENT_IMAGE_TAG }} + docker images --all + - name: Generate parachain artefacts + run: | + ./scripts/litentry/generate_parachain_artefacts.sh + - name: Build litentry parachain docker images + run: | + cd docker + docker-compose -f litentry-parachain.build.yml build + - name: Integration Test ${{ matrix.test }}-${{ matrix.flavor_id }} + timeout-minutes: 30 + run: | + cd docker + docker-compose -f docker-compose.yml -f ${{ matrix.demo_name }}.yml up --no-build --exit-code-from ${{ matrix.demo_name }} -- ${{ matrix.demo_name }} + - name: Stop docker containers + run: | + cd docker + docker compose -f docker-compose.yml -f ${{ matrix.demo_name }}.yml stop + - name: Collect Docker Logs + continue-on-error: true + if: always() + uses: jwalton/gh-docker-logs@v2 + with: + #images: '${{ env.WORKER_IMAGE_TAG }},${{ env.CLIENT_IMAGE_TAG }}' + tail: all + dest: ./${{ env.LOG_DIR }} + + - name: Upload logs + if: always() + uses: actions/upload-artifact@v3 + with: + name: logs-${{ matrix.test }}-${{ matrix.flavor_id }} + path: ./${{ env.LOG_DIR }} + + # Only push docker image when tests are passed on dev branch + push-docker-image: + runs-on: ubuntu-latest + needs: + - integration-tests + if: ${{ success() && (github.event_name == 'push') && (github.ref == 'refs/heads/dev') }} + steps: + - uses: actions/download-artifact@v3 + with: + name: parachain-artifact + + - name: Load docker image + run: | + docker load -i litentry-parachain.tar + - name: Dockerhub login + uses: docker/login-action@v1 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_PASSWORD }} + + - name: Push docker image + run: docker push litentry/litentry-parachain:tee-dev + + release: + name: Draft Release + if: startsWith(github.ref, 'refs/tags/') + runs-on: ubuntu-latest + needs: [build-test, integration-tests] + outputs: + release_url: ${{ steps.create-release.outputs.html_url }} + asset_upload_url: ${{ steps.create-release.outputs.upload_url }} + steps: + - uses: actions/checkout@v3 + + - name: Download Integritee Service + uses: actions/download-artifact@v3 + with: + name: integritee-worker-sidechain-${{ github.sha }} + path: integritee-worker-tmp + + - name: Download Integritee Client + uses: actions/download-artifact@v3 + with: + name: integritee-client-sidechain-${{ github.sha }} + path: integritee-client-tmp + + - name: Download Enclave Signed + uses: actions/download-artifact@v3 + with: + name: enclave-signed-sidechain-${{ github.sha }} + path: enclave-signed-tmp + + - name: Move service binaries + run: mv integritee-worker-tmp/integritee-service ./integritee-demo-validateer + + - name: Move service client binaries + run: mv integritee-client-tmp/integritee-cli ./integritee-client + + - name: Move service client binaries + run: mv enclave-signed-tmp/enclave.signed.so ./enclave.signed.so + + - name: Create required package.json + run: test -f package.json || echo '{}' >package.json + + - name: Changelog + uses: scottbrenner/generate-changelog-action@master + id: Changelog + + - name: Display structure of downloaded files + run: ls -R + working-directory: . + + - name: Release + id: create-release + uses: softprops/action-gh-release@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + body: | + ${{ steps.Changelog.outputs.changelog }} + draft: true + files: | + integritee-client + integritee-demo-validateer + enclave.signed.so diff --git a/tee-worker/.github/workflows/check_labels.yml b/tee-worker/.github/workflows/check_labels.yml new file mode 100644 index 0000000000..3e1c6e4a56 --- /dev/null +++ b/tee-worker/.github/workflows/check_labels.yml @@ -0,0 +1,26 @@ +name: Labels Check + +# only triggerable manually in our case +on: + workflow_dispatch: + +jobs: + A-label-check: + uses: ./.github/workflows/label-checker.yml + with: + predefined_labels: "A0-core,A1-cli,A2-applibs,A3-sidechain,A4-offchain,A5-teeracle,A6-evm,A7-somethingelse" + + B-label-check: + uses: ./.github/workflows/label-checker.yml + with: + predefined_labels: "B0-silent,B1-releasenotes" + + C-label-check: + uses: ./.github/workflows/label-checker.yml + with: + predefined_labels: "C1-low 📌,C3-medium 📣,C7-high ❗️,C9-critical ‼️" + + E-label-check: + uses: ./.github/workflows/label-checker.yml + with: + predefined_labels: "E0-breaksnothing,E3-hardmerge,E5-publicapi,E6-parentchain,E8-breakseverything" diff --git a/tee-worker/.github/workflows/delete-release.yml b/tee-worker/.github/workflows/delete-release.yml new file mode 100644 index 0000000000..b1d0e13750 --- /dev/null +++ b/tee-worker/.github/workflows/delete-release.yml @@ -0,0 +1,65 @@ +name: Delete-Release + +on: + release: + types: [deleted] # should be deleted + +jobs: + purge-image: + name: Delete image from ghcr.io + runs-on: ubuntu-latest + strategy: + matrix: + binary: ["integritee-client", "integritee-demo-validateer"] + steps: + - uses: actions/checkout@v2 + + - name: Set output + id: vars + run: echo "{tag}={$GITHUB_REF#refs/*/}" >> $GITHUB_OUTPUT + + - name: Check output + env: + RELEASE_VERSION: ${{ steps.vars.outputs.tag }} + run: | + echo $RELEASE_VERSION + echo ${{ steps.vars.outputs.tag }} + echo ${{github.event.pull_request.number}} + + - name: Login to DockerHub + if: github.event_name != 'pull_request' + uses: docker/login-action@v1 + with: + username: ${{ secrets.DOCKER_HUB_USERNAME }} + password: ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }} + + # Unfortunately accessing the repo with personal access token is not possible + # Workaround: disable 2FA and user password instead of TOKEN + - name: Delete docker tag + run: | + ORGANIZATION="integritee" + IMAGE="${{ matrix.binary }}" + TAG="${{ steps.vars.outputs.tag }}" + + login_data() { + cat < ~/mrenclave.b58; fi + +# checks +RUN ldd /usr/local/bin/integritee && \ + /usr/local/bin/integritee --version + +ENTRYPOINT ["/usr/local/bin/integritee"] diff --git a/tee-worker/Jenkinsfile b/tee-worker/Jenkinsfile new file mode 100755 index 0000000000..c92d5df20c --- /dev/null +++ b/tee-worker/Jenkinsfile @@ -0,0 +1,104 @@ +pipeline { + agent { + docker { + image 'integritee/integritee-dev:0.1.7' + args ''' + -u root + --privileged + ''' + } + } + options { + timeout(time: 2, unit: 'HOURS') + buildDiscarder(logRotator(numToKeepStr: '14')) + } + stages { + stage('Init rust') { + steps { + sh 'cargo --version' + sh 'rustup show' + sh 'env' + } + } + stage('Build') { + steps { + sh 'export SGX_SDK=/opt/intel/sgxsdk' + sh 'make' + } + } + stage('Archive build output') { + steps { + archiveArtifacts artifacts: 'bin/enclave.signed.so, bin/integritee-*', caseSensitive: false, fingerprint: true, onlyIfSuccessful: true + } + } + stage('Test') { + steps { + sh 'cd cli && cargo test 2>&1 | tee ${WORKSPACE}/test_client.log' + sh 'cd service && cargo test 2>&1 | tee ${WORKSPACE}/test_server.log' + sh 'cd enclave-runtime && cargo test 2>&1 | tee ${WORKSPACE}/test_enclave.log' + } + } + stage('Clippy') { + steps { + sh 'cargo clean' + catchError(buildResult: 'SUCCESS', stageResult: 'FAILURE') { + sh 'cd cli && cargo clippy 2>&1 | tee ${WORKSPACE}/clippy_client.log' + } + catchError(buildResult: 'SUCCESS', stageResult: 'FAILURE') { + sh 'cd worker && cargo clippy 2>&1 | tee ${WORKSPACE}/clippy_worker.log' + } + catchError(buildResult: 'SUCCESS', stageResult: 'FAILURE') { + sh 'cd enclave && cargo clippy 2>&1 | tee ${WORKSPACE}/clippy_enclave.log' + } + } + } + stage('Formatter') { + steps { + catchError(buildResult: 'SUCCESS', stageResult: 'SUCCESS') { + sh 'cargo fmt -- --check > ${WORKSPACE}/fmt.log' + } + } + } + stage('Results') { + steps { + recordIssues( + aggregatingResults: true, + enabledForFailure: true, + qualityGates: [[threshold: 1, type: 'TOTAL', unstable: true]], + tools: [ + groovyScript( + parserId:'clippy-warnings', + pattern: 'clippy_*.log', + reportEncoding: 'UTF-8' + ), + groovyScript( + parserId:'clippy-errors', + pattern: 'clippy_*.log', + reportEncoding: 'UTF-8' + ) + ] + ) + catchError(buildResult: 'SUCCESS', stageResult: 'SUCCESS') { + sh './ci/check_fmt_log.sh' + } + } + } + stage('Archive logs') { + steps { + archiveArtifacts artifacts: '*.log' + } + } + } + post { + unsuccessful { + emailext ( + subject: "Jenkins Build '${env.JOB_NAME} [${env.BUILD_NUMBER}]' is ${currentBuild.currentResult}", + body: "${env.JOB_NAME} build ${env.BUILD_NUMBER} is ${currentBuild.currentResult}\n\nMore info at: ${env.BUILD_URL}", + to: "${env.RECIPIENTS_SUBSTRATEE}" + ) + } + always { + cleanWs() + } + } +} diff --git a/tee-worker/LICENSE b/tee-worker/LICENSE new file mode 100644 index 0000000000..261eeb9e9f --- /dev/null +++ b/tee-worker/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/tee-worker/Makefile b/tee-worker/Makefile new file mode 100755 index 0000000000..e310b2e460 --- /dev/null +++ b/tee-worker/Makefile @@ -0,0 +1,267 @@ +# Copyright 2021 Integritee AG and Supercomputing Systems AG +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +######## Update SGX SDK ######## +# use this manually to update sdk +#include UpdateRustSGXSDK.mk + +######## SGX SDK Settings ######## +SGX_SDK ?= /opt/intel/sgxsdk +SGX_MODE ?= HW +SGX_ARCH ?= x64 +SGX_DEBUG ?= 0 +SGX_PRERELEASE ?= 0 +SGX_PRODUCTION ?= 0 + +######## Worker Feature Settings ######## +# Set sidechain as default feature mode +WORKER_MODE ?= sidechain + +SKIP_WASM_BUILD = 1 +# include the build settings from rust-sgx-sdk +include rust-sgx-sdk/buildenv.mk + +ifeq ($(shell getconf LONG_BIT), 32) + SGX_ARCH := x86 +else ifeq ($(findstring -m32, $(CXXFLAGS)), -m32) + SGX_ARCH := x86 +endif + +ifeq ($(SGX_ARCH), x86) + SGX_COMMON_CFLAGS := -m32 + SGX_LIBRARY_PATH := $(SGX_SDK)/lib + SGX_ENCLAVE_SIGNER := $(SGX_SDK)/bin/x86/sgx_sign + SGX_EDGER8R := $(SGX_SDK)/bin/x86/sgx_edger8r +else + SGX_COMMON_CFLAGS := -m64 + SGX_LIBRARY_PATH := $(SGX_SDK)/lib64 + SGX_ENCLAVE_SIGNER := $(SGX_SDK)/bin/x64/sgx_sign + SGX_EDGER8R := $(SGX_SDK)/bin/x64/sgx_edger8r +endif + +ifeq ($(SGX_DEBUG), 1) +ifeq ($(SGX_PRERELEASE), 1) +$(error Cannot set SGX_DEBUG and SGX_PRERELEASE at the same time!!) +endif +ifeq ($(SGX_PRODUCTION), 1) +$(error Cannot set SGX_DEBUG and SGX_PRODUCTION at the same time!!) +endif +endif + +ifeq ($(SGX_DEBUG), 1) + # we build with cargo --release, even in SGX DEBUG mode + SGX_COMMON_CFLAGS += -O0 -g -ggdb + # cargo sets this automatically, cannot use 'debug' + OUTPUT_PATH := release + CARGO_TARGET := --release +else + SGX_COMMON_CFLAGS += -O2 + OUTPUT_PATH := release + CARGO_TARGET := --release +endif + +SGX_COMMON_CFLAGS += -fstack-protector + +ifeq ($(SGX_PRODUCTION), 1) + SGX_ENCLAVE_MODE = "Production Mode" + SGX_ENCLAVE_CONFIG = "enclave-runtime/Enclave.config.production.xml" + SGX_SIGN_KEY = $(SGX_COMMERCIAL_KEY) + WORKER_FEATURES = --features=production,$(WORKER_MODE),$(ADDITIONAL_FEATURES) +else + SGX_ENCLAVE_MODE = "Development Mode" + SGX_ENCLAVE_CONFIG = "enclave-runtime/Enclave.config.xml" + SGX_SIGN_KEY = "enclave-runtime/Enclave_private.pem" + WORKER_FEATURES = --features=default,$(WORKER_MODE),$(ADDITIONAL_FEATURES) +endif + +CLIENT_FEATURES = --features=$(WORKER_MODE),$(ADDITIONAL_FEATURES) + +# check if running on Jenkins +ifdef BUILD_ID + CARGO_TARGET += --verbose +endif + +######## CUSTOM settings ######## +CUSTOM_LIBRARY_PATH := ./lib +CUSTOM_BIN_PATH := ./bin +CUSTOM_EDL_PATH := ./rust-sgx-sdk/edl +CUSTOM_COMMON_PATH := ./rust-sgx-sdk/common + +######## EDL settings ######## +Enclave_EDL_Files := enclave-runtime/Enclave_t.c enclave-runtime/Enclave_t.h service/Enclave_u.c service/Enclave_u.h + +######## Integritee-service settings ######## +SRC_Files := $(shell find . -type f -name '*.rs') $(shell find . -type f -name 'Cargo.toml') +Worker_Rust_Flags := $(CARGO_TARGET) $(WORKER_FEATURES) +Worker_Include_Paths := -I ./service -I./include -I$(SGX_SDK)/include -I$(CUSTOM_EDL_PATH) +Worker_C_Flags := $(SGX_COMMON_CFLAGS) -fPIC -Wno-attributes $(Worker_Include_Paths) + +Worker_Rust_Path := target/$(OUTPUT_PATH) +Worker_Enclave_u_Object :=service/libEnclave_u.a +Worker_Name := bin/app + +######## Integritee-cli settings ######## +Client_Rust_Flags := $(CARGO_TARGET) $(CLIENT_FEATURES) + +Client_Rust_Path := target/$(OUTPUT_PATH) +Client_Path := bin +Client_Binary := integritee-cli +Client_Name := $(Client_Path)/$(Client_Binary) + +######## Enclave settings ######## +ifneq ($(SGX_MODE), HW) + Trts_Library_Name := sgx_trts_sim + Service_Library_Name := sgx_tservice_sim +else + Trts_Library_Name := sgx_trts + Service_Library_Name := sgx_tservice +endif +Crypto_Library_Name := sgx_tcrypto +KeyExchange_Library_Name := sgx_tkey_exchange +ProtectedFs_Library_Name := sgx_tprotected_fs + +RustEnclave_C_Files := $(wildcard ./enclave-runtime/*.c) +RustEnclave_C_Objects := $(RustEnclave_C_Files:.c=.o) +RustEnclave_Include_Paths := -I$(CUSTOM_COMMON_PATH)/inc -I$(CUSTOM_EDL_PATH) -I$(SGX_SDK)/include -I$(SGX_SDK)/include/tlibc -I$(SGX_SDK)/include/stlport -I$(SGX_SDK)/include/epid -I ./enclave-runtime -I./include + +RustEnclave_Link_Libs := -L$(CUSTOM_LIBRARY_PATH) -lenclave +RustEnclave_Compile_Flags := $(SGX_COMMON_CFLAGS) $(ENCLAVE_CFLAGS) $(RustEnclave_Include_Paths) +RustEnclave_Link_Flags := -Wl,--no-undefined -nostdlib -nodefaultlibs -nostartfiles -L$(SGX_LIBRARY_PATH) \ + -Wl,--whole-archive -l$(Trts_Library_Name) -Wl,--no-whole-archive \ + -Wl,--start-group -lsgx_tstdc -lsgx_tcxx -l$(Crypto_Library_Name) -l$(Service_Library_Name) -l$(ProtectedFs_Library_Name) $(RustEnclave_Link_Libs) -Wl,--end-group \ + -Wl,--version-script=enclave-runtime/Enclave.lds \ + $(ENCLAVE_LDFLAGS) + +RustEnclave_Name := enclave-runtime/enclave.so +Signed_RustEnclave_Name := bin/enclave.signed.so + +######## Targets ######## +.PHONY: all +all: fmt $(Worker_Name) $(Client_Name) $(Signed_RustEnclave_Name) +service: $(Worker_Name) +client: $(Client_Name) +githooks: .git/hooks/pre-commit + +######## EDL objects ######## +$(Enclave_EDL_Files): $(SGX_EDGER8R) enclave-runtime/Enclave.edl + $(SGX_EDGER8R) --trusted enclave-runtime/Enclave.edl --search-path $(SGX_SDK)/include --search-path $(CUSTOM_EDL_PATH) --trusted-dir enclave-runtime + $(SGX_EDGER8R) --untrusted enclave-runtime/Enclave.edl --search-path $(SGX_SDK)/include --search-path $(CUSTOM_EDL_PATH) --untrusted-dir service + @echo "GEN => $(Enclave_EDL_Files)" + +######## Integritee-service objects ######## +service/Enclave_u.o: $(Enclave_EDL_Files) + @$(CC) $(Worker_C_Flags) -c service/Enclave_u.c -o $@ + @echo "CC <= $<" + +$(Worker_Enclave_u_Object): service/Enclave_u.o + $(AR) rcsD $@ $^ + cp $(Worker_Enclave_u_Object) ./lib + +$(Worker_Name): $(Worker_Enclave_u_Object) $(SRC_Files) + @echo + @echo "Building the integritee-service" + @SGX_SDK=$(SGX_SDK) SGX_MODE=$(SGX_MODE) cargo build -p integritee-service $(Worker_Rust_Flags) + @echo "Cargo => $@" + cp $(Worker_Rust_Path)/integritee-service ./bin + +######## Integritee-client objects ######## +$(Client_Name): $(SRC_Files) + @echo + @echo "Building the integritee-cli" + @cargo build -p integritee-cli $(Client_Rust_Flags) + @echo "Cargo => $@" + cp $(Client_Rust_Path)/$(Client_Binary) ./bin + +######## Enclave objects ######## +enclave-runtime/Enclave_t.o: $(Enclave_EDL_Files) + @$(CC) $(RustEnclave_Compile_Flags) -c enclave-runtime/Enclave_t.c -o $@ + @echo "CC <= $<" + +$(RustEnclave_Name): enclave enclave-runtime/Enclave_t.o + @echo Compiling $(RustEnclave_Name) + @$(CXX) enclave-runtime/Enclave_t.o -o $@ $(RustEnclave_Link_Flags) + @echo "LINK => $@" + +$(Signed_RustEnclave_Name): $(RustEnclave_Name) + @echo + @echo "Signing the enclave: $(SGX_ENCLAVE_MODE)" + $(SGX_ENCLAVE_SIGNER) sign -key $(SGX_SIGN_KEY) -enclave $(RustEnclave_Name) -out $@ -config $(SGX_ENCLAVE_CONFIG) + @echo "SIGN => $@" + @echo + @echo "Enclave is in $(SGX_ENCLAVE_MODE)" + +.PHONY: enclave +enclave: + @echo + @echo "Building the enclave" + $(MAKE) -C ./enclave-runtime/ + +.git/hooks/pre-commit: .githooks/pre-commit + @echo "Installing git hooks" + cp .githooks/pre-commit .git/hooks + +.PHONY: clean +clean: + @echo "Removing the compiled files" + @rm -f $(Client_Name) $(Worker_Name) $(RustEnclave_Name) $(Signed_RustEnclave_Name) \ + enclave-runtime/*_t.* \ + service/*_u.* \ + lib/*.a \ + bin/*.bin + @echo "cargo clean in enclave directory" + @cd enclave-runtime && cargo clean + @echo "cargo clean in root directory" + @cargo clean + +.PHONY: fmt +fmt: + @echo "Cargo format all ..." + @cargo fmt --all + @cd enclave-runtime && cargo fmt --all + +.PHONY: pin-sgx +pin-sgx: + @echo "Pin sgx dependencies to 9c1bbd52f188f600a212b57c916124245da1b7fd" + @cd enclave-runtime && cargo update -p sgx_tstd --precise 9c1bbd52f188f600a212b57c916124245da1b7fd + @cargo update -p sgx_tstd --precise 9c1bbd52f188f600a212b57c916124245da1b7fd + +mrenclave: + @$(SGX_ENCLAVE_SIGNER) dump -enclave ./bin/enclave.signed.so -dumpfile df.out && ./extract_identity < df.out && rm df.out + +mrsigner: + @$(SGX_ENCLAVE_SIGNER) dump -enclave ./bin/enclave.signed.so -dumpfile df.out && ./extract_identity --mrsigner < df.out && rm df.out + +.PHONY: identity +identity: mrenclave mrsigner + +.PHONY: help +help: + @echo "Available targets" + @echo " all - builds all targets (default)" + @echo " service - builds the integritee-service" + @echo " client - builds the integritee-cli" + @echo " githooks - installs the git hooks (copy .githooks/pre-commit to .git/hooks)" + @echo "" + @echo " clean - cleanup" + @echo "" + @echo "Compilation options. Prepend them to the make command. Example: 'SGX_MODE=SW make'" + @echo " SGX_MODE" + @echo " HW (default): Use SGX hardware" + @echo " SW: Simulation mode" + @echo " SGX_DEBUG" + @echo " 0 (default): No debug information, optimization level 2, cargo release build" + @echo " 1: Debug information, optimization level 0, cargo debug build" + @echo " SGX_PRODUCTION" + @echo " 0 (default): Using SGX development environment" + @echo " 1: Using SGX production environment" diff --git a/tee-worker/README.md b/tee-worker/README.md new file mode 100755 index 0000000000..fb0c3f0255 --- /dev/null +++ b/tee-worker/README.md @@ -0,0 +1,42 @@ +# tee worker + +This tee-worker is based on [Integritee](https://integritee.network)'s [worker](https://github.com/integritee-network/worker) + +The following is the original README from integritee-worker. + +## Build and Run +Please see our [Integritee Book](https://docs.integritee.network/4-development/4.4-sdk) to learn how to build and run this. + +To start multiple worker and a node with one simple command: Check out [this README](local-setup/README.md). + +## Docker +See [docker/README.md](docker/README.md). + +## Tests + +There are 3 types of tests: +- cargo tests +- enclave tests +- integration tests + +### Cargo Tests +Run +``` +cargo test +``` + +### Enclave Tests +Run + +``` +make +./bin/integritee-service test --all +``` + +### Integration Tests +See [docker/README.md](docker/README.md) + +## Direct calls scalability + +For direct calls, a worker runs a web-socket server inside the enclave. An important factor for scalability is the transaction throughput of a single worker instance, which is in part defined by the maximum number of concurrent socket connections possible. On Linux by default, a process can have a maximum of `1024` concurrent file descriptors (show by `ulimit -n`). +If the web-socket server hits that limit, incoming connections will be declined until one of the established connections is closed. Permanently changing the `ulimit -n` value can be done in the `/etc/security/limits.conf` configuration file. See [this](https://linuxhint.com/permanently_set_ulimit_value/) guide for more information. diff --git a/tee-worker/UpdateRustSGXSDK.mk b/tee-worker/UpdateRustSGXSDK.mk new file mode 100755 index 0000000000..88c95d5dc6 --- /dev/null +++ b/tee-worker/UpdateRustSGXSDK.mk @@ -0,0 +1,33 @@ +# helper script to update the files in rust-sgx-sdk to the lastest version + +GIT = git +CP = cp + +REPO = https://github.com/apache/incubator-teaclave-sgx-sdk +SDK_PATH_GIT = rust-sgx-sdk-github +SDK_PATH = rust-sgx-sdk +VERSION_FILE = rust-sgx-sdk/version +LOCAL_VERSION = $(shell cat $(VERSION_FILE)) +COMMAND = git ls-remote $(REPO) HEAD | awk '{ print $$1 }' +REMOTE_VERSION = $(shell $(COMMAND)) +# or specify the exact hash if you need a non-default branch / tag / commit etc. +#REMOTE_VERSION = 9c1bbd52f188f600a212b57c916124245da1b7fd + +# update the SDK files +all: updatesdk + +updatesdk: +# check for already updated version +ifneq ('$(LOCAL_VERSION)','$(REMOTE_VERSION)') + @echo Local version = $(LOCAL_VERSION) + @echo Remote version = $(REMOTE_VERSION) + + @rm -rf $(SDK_PATH_GIT) + @$(GIT) clone $(REPO) $(SDK_PATH_GIT) + @$(GIT) -C $(SDK_PATH_GIT) checkout $(REMOTE_VERSION) + rsync -a $(SDK_PATH_GIT)/edl $(SDK_PATH) + rsync -a $(SDK_PATH_GIT)/common $(SDK_PATH) + rm -rf $(SDK_PATH_GIT) + @echo $(REMOTE_VERSION) > $(VERSION_FILE) + +endif diff --git a/tee-worker/app-libs/exchange-oracle/Cargo.toml b/tee-worker/app-libs/exchange-oracle/Cargo.toml new file mode 100644 index 0000000000..9bfc9d7c0c --- /dev/null +++ b/tee-worker/app-libs/exchange-oracle/Cargo.toml @@ -0,0 +1,50 @@ +[package] +authors = ["Integritee AG "] +edition = "2021" +name = "ita-exchange-oracle" +version = "0.9.0" + +[dependencies] + +# std dependencies +thiserror = { version = "1.0.26", optional = true } +url = { version = "2.0.0", optional = true } + +# sgx dependencies +sgx_tstd = { rev = "v1.1.3", git = "https://github.com/apache/teaclave-sgx-sdk.git", optional = true } +thiserror_sgx = { package = "thiserror", git = "https://github.com/mesalock-linux/thiserror-sgx", tag = "sgx_1.1.3", optional = true } +url_sgx = { package = "url", git = "https://github.com/mesalock-linux/rust-url-sgx", tag = "sgx_1.1.3", optional = true } + +# no_std dependencies +codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } +lazy_static = { version = "1.1.0", features = ["spin_no_std"] } +log = { version = "0.4", default-features = false } +serde = { version = "1.0", default-features = false, features = ["derive", "alloc"] } +serde_json = { version = "1.0", default-features = false, features = ["alloc"] } +substrate-fixed = { default-features = false, git = "https://github.com/encointer/substrate-fixed", tag = "v0.5.9" } + +# internal dependencies +itc-rest-client = { path = "../../core/rest-client", default-features = false } +itp-enclave-metrics = { path = "../../core-primitives/enclave-metrics", default-features = false } +itp-ocall-api = { path = "../../core-primitives/ocall-api", default-features = false } + +[features] +default = ["std"] +sgx = [ + "itc-rest-client/sgx", + "itp-enclave-metrics/sgx", + "sgx_tstd", + "thiserror_sgx", + "url_sgx", +] +std = [ + "itc-rest-client/std", + "itp-enclave-metrics/std", + "itp-ocall-api/std", + "log/std", + "serde/std", + "serde_json/std", + "substrate-fixed/std", + "thiserror", + "url", +] diff --git a/tee-worker/app-libs/exchange-oracle/src/certificates/amazon_root_ca_a.pem b/tee-worker/app-libs/exchange-oracle/src/certificates/amazon_root_ca_a.pem new file mode 100644 index 0000000000..a6f3e92af5 --- /dev/null +++ b/tee-worker/app-libs/exchange-oracle/src/certificates/amazon_root_ca_a.pem @@ -0,0 +1,20 @@ +-----BEGIN CERTIFICATE----- +MIIDQTCCAimgAwIBAgITBmyfz5m/jAo54vB4ikPmljZbyjANBgkqhkiG9w0BAQsF +ADA5MQswCQYDVQQGEwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6 +b24gUm9vdCBDQSAxMB4XDTE1MDUyNjAwMDAwMFoXDTM4MDExNzAwMDAwMFowOTEL +MAkGA1UEBhMCVVMxDzANBgNVBAoTBkFtYXpvbjEZMBcGA1UEAxMQQW1hem9uIFJv +b3QgQ0EgMTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALJ4gHHKeNXj +ca9HgFB0fW7Y14h29Jlo91ghYPl0hAEvrAIthtOgQ3pOsqTQNroBvo3bSMgHFzZM +9O6II8c+6zf1tRn4SWiw3te5djgdYZ6k/oI2peVKVuRF4fn9tBb6dNqcmzU5L/qw +IFAGbHrQgLKm+a/sRxmPUDgH3KKHOVj4utWp+UhnMJbulHheb4mjUcAwhmahRWa6 +VOujw5H5SNz/0egwLX0tdHA114gk957EWW67c4cX8jJGKLhD+rcdqsq08p8kDi1L +93FcXmn/6pUCyziKrlA4b9v7LWIbxcceVOF34GfID5yHI9Y/QCB/IIDEgEw+OyQm +jgSubJrIqg0CAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMC +AYYwHQYDVR0OBBYEFIQYzIU07LwMlJQuCFmcx7IQTgoIMA0GCSqGSIb3DQEBCwUA +A4IBAQCY8jdaQZChGsV2USggNiMOruYou6r4lK5IpDB/G/wkjUu0yKGX9rbxenDI +U5PMCCjjmCXPI6T53iHTfIUJrU6adTrCC2qJeHZERxhlbI1Bjjt/msv0tadQ1wUs +N+gDS63pYaACbvXy8MWy7Vu33PqUXHeeE6V/Uq2V8viTO96LXFvKWlJbYK8U90vv +o/ufQJVtMVT8QtPHRh8jrdkPSHCa2XV4cdFyQzR1bldZwgJcJmApzyMZFo6IQ6XU +5MsI+yMRQ+hDKXJioaldXgjUkK642M4UwtBV8ob2xJNDd2ZhwLnoQdeXeGADbkpy +rqXRfboQnoZsG4q5WTP468SQvvG5 +-----END CERTIFICATE----- diff --git a/tee-worker/app-libs/exchange-oracle/src/certificates/baltimore_cyber_trust_root_v3.pem b/tee-worker/app-libs/exchange-oracle/src/certificates/baltimore_cyber_trust_root_v3.pem new file mode 100644 index 0000000000..519028c63b --- /dev/null +++ b/tee-worker/app-libs/exchange-oracle/src/certificates/baltimore_cyber_trust_root_v3.pem @@ -0,0 +1,21 @@ +-----BEGIN CERTIFICATE----- +MIIDdzCCAl+gAwIBAgIEAgAAuTANBgkqhkiG9w0BAQUFADBaMQswCQYDVQQGEwJJ +RTESMBAGA1UEChMJQmFsdGltb3JlMRMwEQYDVQQLEwpDeWJlclRydXN0MSIwIAYD +VQQDExlCYWx0aW1vcmUgQ3liZXJUcnVzdCBSb290MB4XDTAwMDUxMjE4NDYwMFoX +DTI1MDUxMjIzNTkwMFowWjELMAkGA1UEBhMCSUUxEjAQBgNVBAoTCUJhbHRpbW9y +ZTETMBEGA1UECxMKQ3liZXJUcnVzdDEiMCAGA1UEAxMZQmFsdGltb3JlIEN5YmVy +VHJ1c3QgUm9vdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKMEuyKr +mD1X6CZymrV51Cni4eiVgLGw41uOKymaZN+hXe2wCQVt2yguzmKiYv60iNoS6zjr +IZ3AQSsBUnuId9Mcj8e6uYi1agnnc+gRQKfRzMpijS3ljwumUNKoUMMo6vWrJYeK +mpYcqWe4PwzV9/lSEy/CG9VwcPCPwBLKBsua4dnKM3p31vjsufFoREJIE9LAwqSu +XmD+tqYF/LTdB1kC1FkYmGP1pWPgkAx9XbIGevOF6uvUA65ehD5f/xXtabz5OTZy +dc93Uk3zyZAsuT3lySNTPx8kmCFcB5kpvcY67Oduhjprl3RjM71oGDHweI12v/ye +jl0qhqdNkNwnGjkCAwEAAaNFMEMwHQYDVR0OBBYEFOWdWTCCR1jMrPoIVDaGezq1 +BE3wMBIGA1UdEwEB/wQIMAYBAf8CAQMwDgYDVR0PAQH/BAQDAgEGMA0GCSqGSIb3 +DQEBBQUAA4IBAQCFDF2O5G9RaEIFoN27TyclhAO992T9Ldcw46QQF+vaKSm2eT92 +9hkTI7gQCvlYpNRhcL0EYWoSihfVCr3FvDB81ukMJY2GQE/szKN+OMY3EU/t3Wgx +jkzSswF07r51XgdIGn9w/xZchMB5hbgF/X++ZRGjD8ACtPhSNzkE1akxehi/oCr0 +Epn3o0WC4zxe9Z2etciefC7IpJ5OCBRLbf1wbWsaY71k5h+3zvDyny67G7fyUIhz +ksLi4xaNmjICq44Y3ekQEe5+NauQrz4wlHrQMz2nZQ/1/I6eYs9HRCwBXbsdtTLS +R9I4LtD+gdwyah617jzV/OeBHRnDJELqYzmp +-----END CERTIFICATE----- diff --git a/tee-worker/app-libs/exchange-oracle/src/coin_gecko.rs b/tee-worker/app-libs/exchange-oracle/src/coin_gecko.rs new file mode 100644 index 0000000000..b77d74ef2d --- /dev/null +++ b/tee-worker/app-libs/exchange-oracle/src/coin_gecko.rs @@ -0,0 +1,196 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +#[cfg(all(not(feature = "std"), feature = "sgx"))] +use crate::sgx_reexport_prelude::*; + +use crate::{ + error::Error, + exchange_rate_oracle::OracleSource, + types::{ExchangeRate, TradingPair}, +}; +use itc_rest_client::{ + http_client::{HttpClient, SendWithCertificateVerification}, + rest_client::RestClient, + RestGet, RestPath, +}; +use lazy_static::lazy_static; +use serde::{Deserialize, Serialize}; +use std::{ + collections::HashMap, + string::{String, ToString}, + time::Duration, + vec::Vec, +}; +use url::Url; + +const COINGECKO_URL: &str = "https://api.coingecko.com"; +const COINGECKO_PARAM_CURRENCY: &str = "vs_currency"; +const COINGECKO_PARAM_COIN: &str = "ids"; +const COINGECKO_PATH: &str = "api/v3/coins/markets"; +const COINGECKO_TIMEOUT: Duration = Duration::from_secs(3u64); +const COINGECKO_ROOT_CERTIFICATE: &str = + include_str!("certificates/baltimore_cyber_trust_root_v3.pem"); + +//TODO: Get CoinGecko coins' id from coingecko API ? For now add here the mapping symbol to id +lazy_static! { + static ref SYMBOL_ID_MAP: HashMap<&'static str, &'static str> = HashMap::from([ + ("DOT", "polkadot"), + ("TEER", "integritee"), + ("KSM", "kusama"), + ("BTC", "bitcoin"), + ]); +} + +/// CoinGecko oracle source. +#[derive(Default)] +pub struct CoinGeckoSource; + +impl CoinGeckoSource { + fn map_crypto_currency_id(trading_pair: &TradingPair) -> Result { + let key = &trading_pair.crypto_currency; + match SYMBOL_ID_MAP.get(key.as_str()) { + Some(v) => Ok(v.to_string()), + None => Err(Error::InvalidCryptoCurrencyId), + } + } +} + +impl OracleSource for CoinGeckoSource { + fn metrics_id(&self) -> String { + "coin_gecko".to_string() + } + + fn request_timeout(&self) -> Option { + Some(COINGECKO_TIMEOUT) + } + + fn base_url(&self) -> Result { + Url::parse(COINGECKO_URL).map_err(|e| Error::Other(format!("{:?}", e).into())) + } + + fn root_certificate_content(&self) -> String { + COINGECKO_ROOT_CERTIFICATE.to_string() + } + + fn execute_exchange_rate_request( + &self, + rest_client: &mut RestClient>, + trading_pair: TradingPair, + ) -> Result { + let fiat_id = trading_pair.fiat_currency.clone(); + let crypto_id = Self::map_crypto_currency_id(&trading_pair)?; + + let response = rest_client + .get_with::( + COINGECKO_PATH.to_string(), + &[(COINGECKO_PARAM_CURRENCY, &fiat_id), (COINGECKO_PARAM_COIN, &crypto_id)], + ) + .map_err(Error::RestClient)?; + + let list = response.0; + if list.is_empty() { + return Err(Error::NoValidData(COINGECKO_URL.to_string(), trading_pair.key())) + } + + match list[0].current_price { + Some(r) => Ok(ExchangeRate::from_num(r)), + None => Err(Error::EmptyExchangeRate(trading_pair)), + } + } +} + +#[derive(Serialize, Deserialize, Debug)] +struct CoinGeckoMarketStruct { + id: String, + symbol: String, + name: String, + current_price: Option, + last_updated: Option, +} + +#[derive(Serialize, Deserialize, Debug)] +struct CoinGeckoMarket(pub Vec); + +impl RestPath for CoinGeckoMarket { + fn get_path(path: String) -> Result { + Ok(path) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::{ + exchange_rate_oracle::ExchangeRateOracle, mock::MetricsExporterMock, GetExchangeRate, + }; + use core::assert_matches::assert_matches; + use std::sync::Arc; + + type TestCoinGeckoClient = ExchangeRateOracle; + + fn get_coin_gecko_crypto_currency_id(crypto_currency: &str) -> Result { + let trading_pair = TradingPair { + crypto_currency: crypto_currency.to_string(), + fiat_currency: "USD".to_string(), + }; + CoinGeckoSource::map_crypto_currency_id(&trading_pair) + } + + #[test] + fn crypto_currency_id_works_for_dot() { + let coin_id = get_coin_gecko_crypto_currency_id("DOT").unwrap(); + assert_eq!(&coin_id, "polkadot"); + } + + #[test] + fn crypto_currency_id_works_for_teer() { + let coin_id = get_coin_gecko_crypto_currency_id("TEER").unwrap(); + assert_eq!(&coin_id, "integritee"); + } + + #[test] + fn crypto_currency_id_works_for_ksm() { + let coin_id = get_coin_gecko_crypto_currency_id("KSM").unwrap(); + assert_eq!(&coin_id, "kusama"); + } + + #[test] + fn crypto_currency_id_works_for_btc() { + let coin_id = get_coin_gecko_crypto_currency_id("BTC").unwrap(); + assert_eq!(&coin_id, "bitcoin"); + } + + #[test] + fn crypto_currency_id_fails_for_undefined_crypto_currency() { + let result = get_coin_gecko_crypto_currency_id("Undefined"); + assert_matches!(result, Err(Error::InvalidCryptoCurrencyId)); + } + + #[test] + fn get_exchange_rate_for_undefined_fiat_currency_fails() { + let coin_gecko_client = create_coin_gecko_client(); + let trading_pair = + TradingPair { crypto_currency: "DOT".to_string(), fiat_currency: "CH".to_string() }; + let result = coin_gecko_client.get_exchange_rate(trading_pair); + assert_matches!(result, Err(Error::RestClient(_))); + } + + fn create_coin_gecko_client() -> TestCoinGeckoClient { + TestCoinGeckoClient::new(CoinGeckoSource {}, Arc::new(MetricsExporterMock::default())) + } +} diff --git a/tee-worker/app-libs/exchange-oracle/src/coin_market_cap.rs b/tee-worker/app-libs/exchange-oracle/src/coin_market_cap.rs new file mode 100644 index 0000000000..3a6148947c --- /dev/null +++ b/tee-worker/app-libs/exchange-oracle/src/coin_market_cap.rs @@ -0,0 +1,226 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ +#[cfg(all(not(feature = "std"), feature = "sgx"))] +use crate::sgx_reexport_prelude::*; + +use crate::{ + error::Error, + exchange_rate_oracle::OracleSource, + types::{ExchangeRate, TradingPair}, +}; +use itc_rest_client::{ + http_client::{HttpClient, SendWithCertificateVerification}, + rest_client::RestClient, + RestGet, RestPath, +}; +use lazy_static::lazy_static; +use serde::{Deserialize, Serialize}; +use std::{ + collections::{BTreeMap, HashMap}, + env, + string::{String, ToString}, + time::Duration, +}; +use url::Url; + +const COINMARKETCAP_URL: &str = "https://pro-api.coinmarketcap.com"; +const COINMARKETCAP_KEY_PARAM: &str = "CMC_PRO_API_KEY"; +const FIAT_CURRENCY_PARAM: &str = "convert_id"; +const CRYPTO_CURRENCY_PARAM: &str = "id"; +const COINMARKETCAP_PATH: &str = "v2/cryptocurrency/quotes/latest"; // API endpoint to get the exchange rate with a basic API plan (free) +const COINMARKETCAP_TIMEOUT: Duration = Duration::from_secs(3u64); +const COINMARKETCAP_ROOT_CERTIFICATE: &str = include_str!("certificates/amazon_root_ca_a.pem"); + +lazy_static! { + static ref CRYPTO_SYMBOL_ID_MAP: HashMap<&'static str, &'static str> = + HashMap::from([("DOT", "6636"), ("TEER", "13323"), ("KSM", "5034"), ("BTC", "1"),]); + static ref COINMARKETCAP_KEY: String = env::var("COINMARKETCAP_KEY").unwrap_or_default(); +} + +lazy_static! { + static ref FIAT_SYMBOL_ID_MAP: HashMap<&'static str, &'static str> = + HashMap::from([("USD", "2781"), ("EUR", "2790"), ("CHF", "2785"), ("JPY", "2797"),]); +} + +#[derive(Default)] +pub struct CoinMarketCapSource; + +impl CoinMarketCapSource { + fn map_crypto_currency_id(trading_pair: &TradingPair) -> Result { + CRYPTO_SYMBOL_ID_MAP + .get(trading_pair.crypto_currency.as_str()) + .map(|v| v.to_string()) + .ok_or(Error::InvalidCryptoCurrencyId) + } + + fn map_fiat_currency_id(trading_pair: &TradingPair) -> Result { + FIAT_SYMBOL_ID_MAP + .get(trading_pair.fiat_currency.as_str()) + .map(|v| v.to_string()) + .ok_or(Error::InvalidFiatCurrencyId) + } +} + +impl OracleSource for CoinMarketCapSource { + fn metrics_id(&self) -> String { + "coin_market_cap".to_string() + } + + fn request_timeout(&self) -> Option { + Some(COINMARKETCAP_TIMEOUT) + } + + fn base_url(&self) -> Result { + Url::parse(COINMARKETCAP_URL).map_err(|e| Error::Other(format!("{:?}", e).into())) + } + + fn root_certificate_content(&self) -> String { + COINMARKETCAP_ROOT_CERTIFICATE.to_string() + } + + fn execute_exchange_rate_request( + &self, + rest_client: &mut RestClient>, + trading_pair: TradingPair, + ) -> Result { + let fiat_id = Self::map_fiat_currency_id(&trading_pair)?; + let crypto_id = Self::map_crypto_currency_id(&trading_pair)?; + + let response = rest_client + .get_with::( + COINMARKETCAP_PATH.to_string(), + &[ + (FIAT_CURRENCY_PARAM, &fiat_id), + (CRYPTO_CURRENCY_PARAM, &crypto_id), + (COINMARKETCAP_KEY_PARAM, &COINMARKETCAP_KEY), + ], + ) + .map_err(Error::RestClient)?; + + let data_struct = response.0; + + let data = match data_struct.data.get(&crypto_id) { + Some(d) => d, + None => + return Err(Error::NoValidData( + COINMARKETCAP_URL.to_string(), + trading_pair.crypto_currency, + )), + }; + + let quote = match data.quote.get(&fiat_id) { + Some(q) => q, + None => + return Err(Error::NoValidData(COINMARKETCAP_URL.to_string(), trading_pair.key())), + }; + match quote.price { + Some(r) => Ok(ExchangeRate::from_num(r)), + None => Err(Error::EmptyExchangeRate(trading_pair)), + } + } +} + +#[derive(Serialize, Deserialize, Debug)] +struct DataStruct { + id: Option, + name: String, + symbol: String, + quote: BTreeMap, +} + +#[derive(Serialize, Deserialize, Debug)] +struct QuoteStruct { + price: Option, + last_updated: Option, +} + +#[derive(Serialize, Deserialize, Debug)] +struct CoinMarketCapMarketStruct { + data: BTreeMap, +} + +#[derive(Serialize, Deserialize, Debug)] +struct CoinMarketCapMarket(pub CoinMarketCapMarketStruct); + +impl RestPath for CoinMarketCapMarket { + fn get_path(path: String) -> Result { + Ok(path) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::{ + exchange_rate_oracle::ExchangeRateOracle, mock::MetricsExporterMock, GetExchangeRate, + }; + use core::assert_matches::assert_matches; + use std::sync::Arc; + + type TestClient = ExchangeRateOracle; + + fn get_coin_market_cap_crypto_currency_id(crypto_currency: &str) -> Result { + let trading_pair = TradingPair { + crypto_currency: crypto_currency.to_string(), + fiat_currency: "USD".to_string(), + }; + CoinMarketCapSource::map_crypto_currency_id(&trading_pair) + } + + #[test] + fn crypto_currency_id_works_for_dot() { + let coin_id = get_coin_market_cap_crypto_currency_id("DOT").unwrap(); + assert_eq!(&coin_id, "6636"); + } + + #[test] + fn crypto_currency_id_works_for_teer() { + let coin_id = get_coin_market_cap_crypto_currency_id("TEER").unwrap(); + assert_eq!(&coin_id, "13323"); + } + + #[test] + fn crypto_currency_id_works_for_ksm() { + let coin_id = get_coin_market_cap_crypto_currency_id("KSM").unwrap(); + assert_eq!(&coin_id, "5034"); + } + + #[test] + fn crypto_currency_id_works_for_btc() { + let coin_id = get_coin_market_cap_crypto_currency_id("BTC").unwrap(); + assert_eq!(&coin_id, "1"); + } + + #[test] + fn crypto_currency_id_fails_for_undefined_crypto_currency() { + let coin_id = get_coin_market_cap_crypto_currency_id("Undefined"); + assert_matches!(coin_id, Err(Error::InvalidCryptoCurrencyId)); + } + + #[test] + fn get_exchange_rate_for_undefined_fiat_currency_fails() { + let coin_market_cap_client = create_client(); + let trading_pair = + TradingPair { crypto_currency: "DOT".to_string(), fiat_currency: "CH".to_string() }; + let result = coin_market_cap_client.get_exchange_rate(trading_pair); + assert_matches!(result, Err(Error::InvalidFiatCurrencyId)); + } + + fn create_client() -> TestClient { + TestClient::new(CoinMarketCapSource {}, Arc::new(MetricsExporterMock::default())) + } +} diff --git a/tee-worker/app-libs/exchange-oracle/src/error.rs b/tee-worker/app-libs/exchange-oracle/src/error.rs new file mode 100644 index 0000000000..df72280f34 --- /dev/null +++ b/tee-worker/app-libs/exchange-oracle/src/error.rs @@ -0,0 +1,39 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +#[cfg(all(not(feature = "std"), feature = "sgx"))] +use crate::sgx_reexport_prelude::*; + +use crate::types::TradingPair; +use std::{boxed::Box, string::String}; + +/// Exchange rate error +#[derive(Debug, thiserror::Error)] +pub enum Error { + #[error("Rest client error")] + RestClient(#[from] itc_rest_client::error::Error), + #[error("Could not retrieve any data from {0} for {1}")] + NoValidData(String, String), + #[error("Value for exchange rate is null")] + EmptyExchangeRate(TradingPair), + #[error("Invalid id for crypto currency")] + InvalidCryptoCurrencyId, + #[error("Invalid id for fiat currency")] + InvalidFiatCurrencyId, + #[error(transparent)] + Other(#[from] Box), +} diff --git a/tee-worker/app-libs/exchange-oracle/src/exchange_rate_oracle.rs b/tee-worker/app-libs/exchange-oracle/src/exchange_rate_oracle.rs new file mode 100644 index 0000000000..87a5247726 --- /dev/null +++ b/tee-worker/app-libs/exchange-oracle/src/exchange_rate_oracle.rs @@ -0,0 +1,137 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +#[cfg(all(not(feature = "std"), feature = "sgx"))] +use crate::sgx_reexport_prelude::*; + +use crate::{ + metrics_exporter::ExportMetrics, + types::{ExchangeRate, TradingPair}, + Error, GetExchangeRate, +}; +use core::time::Duration; +use itc_rest_client::{ + http_client::{HttpClient, SendWithCertificateVerification}, + rest_client::RestClient, +}; +use log::*; +use std::{string::String, sync::Arc, time::Instant}; +use url::Url; + +/// Oracle source trait used by the `ExchangeRateOracle` (strategy pattern). +pub trait OracleSource: Default { + fn metrics_id(&self) -> String; + + fn request_timeout(&self) -> Option; + + fn base_url(&self) -> Result; + + /// The server's root certificate. A valid certificate is required to open a tls connection + fn root_certificate_content(&self) -> String; + + fn execute_exchange_rate_request( + &self, + rest_client: &mut RestClient>, + trading_pair: TradingPair, + ) -> Result; +} + +pub struct ExchangeRateOracle { + oracle_source: OracleSourceType, + metrics_exporter: Arc, +} + +impl ExchangeRateOracle +where + OracleSourceType: OracleSource, + MetricsExporter: ExportMetrics, +{ + pub fn new(oracle_source: OracleSourceType, metrics_exporter: Arc) -> Self { + ExchangeRateOracle { oracle_source, metrics_exporter } + } +} + +impl GetExchangeRate + for ExchangeRateOracle +where + OracleSourceType: OracleSource, + MetricsExporter: ExportMetrics, +{ + fn get_exchange_rate(&self, trading_pair: TradingPair) -> Result<(ExchangeRate, Url), Error> { + let source_id = self.oracle_source.metrics_id(); + self.metrics_exporter.increment_number_requests(source_id.clone()); + + let base_url = self.oracle_source.base_url()?; + let root_certificate = self.oracle_source.root_certificate_content(); + + debug!("Get exchange rate from URI: {}, trading pair: {:?}", base_url, trading_pair); + + let http_client = HttpClient::new( + SendWithCertificateVerification::new(root_certificate), + true, + self.oracle_source.request_timeout(), + None, + None, + ); + let mut rest_client = RestClient::new(http_client, base_url.clone()); + + let timer_start = Instant::now(); + + match self + .oracle_source + .execute_exchange_rate_request(&mut rest_client, trading_pair.clone()) + { + Ok(exchange_rate) => { + self.metrics_exporter.record_response_time(source_id.clone(), timer_start); + self.metrics_exporter + .update_exchange_rate(source_id, exchange_rate, trading_pair); + + debug!("Successfully executed exchange rate request"); + Ok((exchange_rate, base_url)) + }, + Err(e) => Err(e), + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::mock::{MetricsExporterMock, OracleSourceMock}; + + type TestOracle = ExchangeRateOracle; + + #[test] + fn get_exchange_rate_updates_metrics() { + let metrics_exporter = Arc::new(MetricsExporterMock::default()); + let test_client = TestOracle::new(OracleSourceMock {}, metrics_exporter.clone()); + + let trading_pair = + TradingPair { crypto_currency: "BTC".to_string(), fiat_currency: "USD".to_string() }; + let _bit_usd = test_client.get_exchange_rate(trading_pair.clone()).unwrap(); + + assert_eq!(1, metrics_exporter.get_number_request()); + assert_eq!(1, metrics_exporter.get_response_times().len()); + assert_eq!(1, metrics_exporter.get_exchange_rates().len()); + + let (metric_trading_pair, exchange_rate) = + metrics_exporter.get_exchange_rates().first().unwrap().clone(); + + assert_eq!(trading_pair, metric_trading_pair); + assert_eq!(ExchangeRate::from_num(42.3f32), exchange_rate); + } +} diff --git a/tee-worker/app-libs/exchange-oracle/src/lib.rs b/tee-worker/app-libs/exchange-oracle/src/lib.rs new file mode 100644 index 0000000000..82c57ea0cf --- /dev/null +++ b/tee-worker/app-libs/exchange-oracle/src/lib.rs @@ -0,0 +1,84 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +#![cfg_attr(not(feature = "std"), no_std)] +#![cfg_attr(test, feature(assert_matches))] + +#[cfg(all(feature = "std", feature = "sgx"))] +compile_error!("feature \"std\" and feature \"sgx\" cannot be enabled at the same time"); + +#[cfg(all(not(feature = "std"), feature = "sgx"))] +#[macro_use] +extern crate sgx_tstd as std; + +// re-export module to properly feature gate sgx and regular std environment +#[cfg(all(not(feature = "std"), feature = "sgx"))] +pub mod sgx_reexport_prelude { + pub use thiserror_sgx as thiserror; + pub use url_sgx as url; +} + +#[cfg(all(not(feature = "std"), feature = "sgx"))] +use crate::sgx_reexport_prelude::*; + +use crate::{ + coin_gecko::CoinGeckoSource, + coin_market_cap::CoinMarketCapSource, + error::Error, + exchange_rate_oracle::ExchangeRateOracle, + metrics_exporter::MetricsExporter, + types::{ExchangeRate, TradingPair}, +}; +use itp_ocall_api::EnclaveMetricsOCallApi; +use std::sync::Arc; +use url::Url; + +pub mod coin_gecko; +pub mod coin_market_cap; +pub mod error; +pub mod exchange_rate_oracle; +pub mod metrics_exporter; +pub mod types; + +#[cfg(test)] +mod mock; + +#[cfg(test)] +mod test; + +pub type CoinGeckoExchangeRateOracle = + ExchangeRateOracle>; + +pub type CoinMarketCapExchangeRateOracle = + ExchangeRateOracle>; + +pub fn create_coin_gecko_oracle( + ocall_api: Arc, +) -> CoinGeckoExchangeRateOracle { + ExchangeRateOracle::new(CoinGeckoSource {}, Arc::new(MetricsExporter::new(ocall_api))) +} + +pub fn create_coin_market_cap_oracle( + ocall_api: Arc, +) -> CoinMarketCapExchangeRateOracle { + ExchangeRateOracle::new(CoinMarketCapSource {}, Arc::new(MetricsExporter::new(ocall_api))) +} + +pub trait GetExchangeRate { + /// Get the cryptocurrency/fiat_currency exchange rate + fn get_exchange_rate(&self, trading_pair: TradingPair) -> Result<(ExchangeRate, Url), Error>; +} diff --git a/tee-worker/app-libs/exchange-oracle/src/metrics_exporter.rs b/tee-worker/app-libs/exchange-oracle/src/metrics_exporter.rs new file mode 100644 index 0000000000..1915e8f486 --- /dev/null +++ b/tee-worker/app-libs/exchange-oracle/src/metrics_exporter.rs @@ -0,0 +1,85 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +use crate::types::{ExchangeRate, TradingPair}; +use itp_enclave_metrics::{EnclaveMetric, ExchangeRateOracleMetric}; +use itp_ocall_api::EnclaveMetricsOCallApi; +use log::error; +use std::{string::String, sync::Arc, time::Instant}; + +/// Trait to export metrics for any Teeracle. +pub trait ExportMetrics { + fn increment_number_requests(&self, source: String); + + fn record_response_time(&self, source: String, timer: Instant); + + fn update_exchange_rate( + &self, + source: String, + exchange_rate: ExchangeRate, + trading_pair: TradingPair, + ); +} + +/// Metrics exporter implementation. +pub struct MetricsExporter { + ocall_api: Arc, +} + +impl MetricsExporter +where + OCallApi: EnclaveMetricsOCallApi, +{ + pub fn new(ocall_api: Arc) -> Self { + MetricsExporter { ocall_api } + } + + fn update_metric(&self, metric: ExchangeRateOracleMetric) { + if let Err(e) = self.ocall_api.update_metric(EnclaveMetric::ExchangeRateOracle(metric)) { + error!("Failed to update enclave metric, sgx_status_t: {}", e) + } + } +} + +impl ExportMetrics for MetricsExporter +where + OCallApi: EnclaveMetricsOCallApi, +{ + fn increment_number_requests(&self, source: String) { + self.update_metric(ExchangeRateOracleMetric::NumberRequestsIncrement(source)); + } + + fn record_response_time(&self, source: String, timer: Instant) { + self.update_metric(ExchangeRateOracleMetric::ResponseTime( + source, + timer.elapsed().as_millis(), + )); + } + + fn update_exchange_rate( + &self, + source: String, + exchange_rate: ExchangeRate, + trading_pair: TradingPair, + ) { + self.update_metric(ExchangeRateOracleMetric::ExchangeRate( + source, + trading_pair.key(), + exchange_rate, + )); + } +} diff --git a/tee-worker/app-libs/exchange-oracle/src/mock.rs b/tee-worker/app-libs/exchange-oracle/src/mock.rs new file mode 100644 index 0000000000..7b9b365bad --- /dev/null +++ b/tee-worker/app-libs/exchange-oracle/src/mock.rs @@ -0,0 +1,106 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +#[cfg(feature = "sgx")] +use std::sync::SgxRwLock as RwLock; + +#[cfg(feature = "std")] +use std::sync::RwLock; + +use crate::{ + error::Error, exchange_rate_oracle::OracleSource, metrics_exporter::ExportMetrics, + types::ExchangeRate, TradingPair, +}; +use itc_rest_client::{ + http_client::{HttpClient, SendWithCertificateVerification}, + rest_client::RestClient, +}; +use std::{ + time::{Duration, Instant}, + vec::Vec, +}; +use url::Url; + +/// Mock metrics exporter. +#[derive(Default)] +pub(crate) struct MetricsExporterMock { + number_requests: RwLock, + response_times: RwLock>, + exchange_rates: RwLock>, +} + +impl MetricsExporterMock { + pub fn get_number_request(&self) -> u64 { + *self.number_requests.read().unwrap() + } + + pub fn get_response_times(&self) -> Vec { + self.response_times.read().unwrap().clone() + } + + pub fn get_exchange_rates(&self) -> Vec<(TradingPair, ExchangeRate)> { + self.exchange_rates.read().unwrap().clone() + } +} + +impl ExportMetrics for MetricsExporterMock { + fn increment_number_requests(&self, _source: String) { + (*self.number_requests.write().unwrap()) += 1; + } + + fn record_response_time(&self, _source: String, timer: Instant) { + self.response_times.write().unwrap().push(timer.elapsed().as_millis()); + } + + fn update_exchange_rate( + &self, + _source: String, + exchange_rate: ExchangeRate, + trading_pair: TradingPair, + ) { + self.exchange_rates.write().unwrap().push((trading_pair, exchange_rate)); + } +} + +/// Mock oracle source. +#[derive(Default)] +pub(crate) struct OracleSourceMock; + +impl OracleSource for OracleSourceMock { + fn metrics_id(&self) -> String { + "source_mock".to_string() + } + + fn request_timeout(&self) -> Option { + None + } + + fn base_url(&self) -> Result { + Url::parse("https://mock.base.url").map_err(|e| Error::Other(format!("{:?}", e).into())) + } + + fn root_certificate_content(&self) -> String { + "MOCK_CERTIFICATE".to_string() + } + fn execute_exchange_rate_request( + &self, + _rest_client: &mut RestClient>, + _trading_pair: TradingPair, + ) -> Result { + Ok(ExchangeRate::from_num(42.3f32)) + } +} diff --git a/tee-worker/app-libs/exchange-oracle/src/test.rs b/tee-worker/app-libs/exchange-oracle/src/test.rs new file mode 100644 index 0000000000..459da109cf --- /dev/null +++ b/tee-worker/app-libs/exchange-oracle/src/test.rs @@ -0,0 +1,96 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +//! Integration tests for concrete exchange rate oracle implementations. +//! Uses real HTTP requests, so the sites must be available for these tests. + +use crate::{ + coin_gecko::CoinGeckoSource, + coin_market_cap::CoinMarketCapSource, + error::Error, + exchange_rate_oracle::{ExchangeRateOracle, OracleSource}, + mock::MetricsExporterMock, + GetExchangeRate, TradingPair, +}; +use core::assert_matches::assert_matches; +use std::sync::Arc; +use substrate_fixed::transcendental::ZERO; + +type TestOracle = ExchangeRateOracle; + +#[test] +#[ignore = "requires API key for CoinMarketCap"] +fn get_exchange_rate_from_coin_market_cap_works() { + test_suite_exchange_rates::(); +} + +#[test] +#[ignore = "requires external coin gecko service, disabled temporarily"] +fn get_exchange_rate_from_coin_gecko_works() { + test_suite_exchange_rates::(); +} + +#[test] +fn get_exchange_rate_for_undefined_coin_market_cap_crypto_currency_fails() { + get_exchange_rate_for_undefined_crypto_currency_fails::(); +} + +#[test] +fn get_exchange_rate_for_undefined_coin_gecko_crypto_currency_fails() { + get_exchange_rate_for_undefined_crypto_currency_fails::(); +} + +fn create_exchange_rate_oracle() -> TestOracle { + let oracle_source = OracleSourceType::default(); + ExchangeRateOracle::new(oracle_source, Arc::new(MetricsExporterMock::default())) +} + +fn get_exchange_rate_for_undefined_crypto_currency_fails() { + let oracle = create_exchange_rate_oracle::(); + let trading_pair = TradingPair { + crypto_currency: "invalid_coin".to_string(), + fiat_currency: "USD".to_string(), + }; + let result = oracle.get_exchange_rate(trading_pair); + assert_matches!(result, Err(Error::InvalidCryptoCurrencyId)); +} + +fn test_suite_exchange_rates() { + let oracle = create_exchange_rate_oracle::(); + let dot_to_usd = + TradingPair { crypto_currency: "DOT".to_string(), fiat_currency: "USD".to_string() }; + let dot_usd = oracle.get_exchange_rate(dot_to_usd).unwrap().0; + assert!(dot_usd > 0f32); + let btc_to_usd = + TradingPair { crypto_currency: "BTC".to_string(), fiat_currency: "USD".to_string() }; + let bit_usd = oracle.get_exchange_rate(btc_to_usd).unwrap().0; + assert!(bit_usd > 0f32); + let dot_to_chf = + TradingPair { crypto_currency: "DOT".to_string(), fiat_currency: "CHF".to_string() }; + let dot_chf = oracle.get_exchange_rate(dot_to_chf).unwrap().0; + assert!(dot_chf > 0f32); + let bit_to_chf = + TradingPair { crypto_currency: "BTC".to_string(), fiat_currency: "CHF".to_string() }; + let bit_chf = oracle.get_exchange_rate(bit_to_chf).unwrap().0; + + // Ensure that get_exchange_rate returns a positive rate + assert!(dot_usd > ZERO); + + // Ensure that get_exchange_rate returns a valid value by checking + // that the values obtained for DOT/BIT from different exchange rates are the same + assert_eq!((dot_usd / bit_usd).round(), (dot_chf / bit_chf).round()); +} diff --git a/tee-worker/app-libs/exchange-oracle/src/types.rs b/tee-worker/app-libs/exchange-oracle/src/types.rs new file mode 100644 index 0000000000..3305592b9f --- /dev/null +++ b/tee-worker/app-libs/exchange-oracle/src/types.rs @@ -0,0 +1,37 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +use codec::{Decode, Encode}; +use std::string::String; +use substrate_fixed::types::U32F32; + +/// Market identifier for order +#[derive(Debug, Clone, Encode, Decode, Eq, PartialEq)] +pub struct TradingPair { + pub crypto_currency: String, + pub fiat_currency: String, +} + +impl TradingPair { + pub fn key(self) -> String { + format!("{}/{}", self.crypto_currency, self.fiat_currency) + } +} + +/// TODO Fix https://github.com/integritee-network/pallets/issues/71 and get it from https://github.com/integritee-network/pallets.git +/// Teeracle types +pub type ExchangeRate = U32F32; diff --git a/tee-worker/app-libs/sgx-runtime/Cargo.toml b/tee-worker/app-libs/sgx-runtime/Cargo.toml new file mode 100644 index 0000000000..3c32d89ed6 --- /dev/null +++ b/tee-worker/app-libs/sgx-runtime/Cargo.toml @@ -0,0 +1,97 @@ +[package] +authors = ["Integritee AG "] +edition = "2021" +name = "ita-sgx-runtime" +version = "0.9.0" + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +[dependencies] +hex-literal = { version = "0.3.4", optional = true } +serde = { version = "1.0", optional = true, features = ["derive"] } +# alias "parity-scale-code" to "codec" +codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } +scale-info = { version = "2.0.1", default-features = false, features = ["derive"] } + +# local dependencies +itp-sgx-runtime-primitives = { path = "../../core-primitives/sgx-runtime-primitives", default-features = false } + +# Substrate dependencies +frame-benchmarking = { optional = true, default-features = false, git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.29" } +frame-executive = { default-features = false, git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.29" } +frame-support = { default-features = false, git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.29" } +frame-system = { default-features = false, git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.29" } +frame-system-benchmarking = { optional = true, default-features = false, git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.29" } +frame-system-rpc-runtime-api = { default-features = false, git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.29" } +pallet-aura = { default-features = false, git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.29" } +pallet-balances = { default-features = false, git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.29" } +pallet-grandpa = { default-features = false, git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.29" } +pallet-randomness-collective-flip = { default-features = false, git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.29" } +pallet-sudo = { default-features = false, git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.29" } +pallet-timestamp = { default-features = false, git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.29" } +pallet-transaction-payment = { default-features = false, git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.29" } +pallet-transaction-payment-rpc-runtime-api = { default-features = false, git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.29" } +sp-api = { default-features = false, git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.29" } +sp-block-builder = { default-features = false, git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.29" } +sp-consensus-aura = { default-features = false, git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.29" } +sp-core = { default-features = false, git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.29" } +sp-inherents = { default-features = false, git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.29" } +sp-offchain = { default-features = false, git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.29" } +sp-runtime = { default-features = false, git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.29" } +sp-session = { default-features = false, git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.29" } +sp-std = { default-features = false, git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.29" } +sp-transaction-pool = { default-features = false, git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.29" } +sp-version = { default-features = false, git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.29" } + +# Integritee dependencies +pallet-evm = { default-features = false, optional = true, git = "https://github.com/integritee-network/frontier.git", branch = "polkadot-v0.9.29" } +pallet-parentchain = { default-features = false, git = "https://github.com/integritee-network/pallets.git", branch = "master" } + +# Litentry +litentry-primitives = { path = "../../litentry/primitives", default-features = false } +pallet-identity-management = { path = "../../litentry/pallets/identity-management", default-features = false } + +[features] +default = ["std"] +# Compile the sgx-runtime with evm-pallet support in `no_std`. +evm = ["pallet-evm"] +# Compile the sgx-runtime with evm-pallet support in `std`. +evm_std = [ + "evm", # Activate the `feature = evm` for the compiler flags. + "std", + "pallet-evm/std", +] +runtime-benchmarks = [ + "hex-literal", + "frame-benchmarking", + "frame-support/runtime-benchmarks", + "frame-system-benchmarking", + "frame-system/runtime-benchmarks", + "pallet-balances/runtime-benchmarks", + "pallet-timestamp/runtime-benchmarks", + "sp-runtime/runtime-benchmarks", +] +std = [ + "codec/std", + "scale-info/std", + "serde", + "itp-sgx-runtime-primitives/std", + "frame-executive/std", + "frame-support/std", + "frame-system/std", + "pallet-balances/std", + "pallet-sudo/std", + "pallet-timestamp/std", + "pallet-transaction-payment/std", + "pallet-parentchain/std", + "pallet-identity-management/std", + "pallet-grandpa/std", + "sp-api/std", + "sp-core/std", + "sp-inherents/std", + "sp-runtime/std", + "sp-std/std", + "sp-version/std", + "sp-consensus-aura/std", +] diff --git a/tee-worker/app-libs/sgx-runtime/src/evm.rs b/tee-worker/app-libs/sgx-runtime/src/evm.rs new file mode 100644 index 0000000000..cf5c463d6d --- /dev/null +++ b/tee-worker/app-libs/sgx-runtime/src/evm.rs @@ -0,0 +1,83 @@ +//! Adds the `pallet-evm` support for the `sgx-runtime. + +// Import types from the crate root including the ones generated by the `construct_runtime!` macro. +use crate::{Balances, Event, Runtime, NORMAL_DISPATCH_RATIO}; +use frame_support::{ + pallet_prelude::Weight, parameter_types, weights::constants::WEIGHT_PER_SECOND, +}; +use sp_core::{H160, U256}; +use sp_runtime::traits::BlakeTwo256; + +pub use pallet_evm::{ + AddressMapping, Call as EvmCall, EnsureAddressTruncated, FeeCalculator, GasWeightMapping, + HashedAddressMapping as GenericHashedAddressMapping, SubstrateBlockHashMapping, +}; + +pub type HashedAddressMapping = GenericHashedAddressMapping; + +/// Maximum weight per block +pub const MAXIMUM_BLOCK_WEIGHT: Weight = WEIGHT_PER_SECOND.saturating_div(2); + +// FIXME: For now just a random value. +pub struct FixedGasPrice; +impl FeeCalculator for FixedGasPrice { + fn min_gas_price() -> (U256, Weight) { + (1.into(), Weight::from_ref_time(1)) + } +} + +/// Current approximation of the gas/s consumption considering +/// EVM execution over compiled WASM (on 4.4Ghz CPU). +/// Given the 500ms Weight, from which 75% only are used for transactions, +/// the total EVM execution gas limit is: GAS_PER_SECOND * 0.500 * 0.75 ~= 15_000_000. +pub const GAS_PER_SECOND: u64 = 40_000_000; + +/// Approximate ratio of the amount of Weight per Gas. +/// u64 works for approximations because Weight is a very small unit compared to gas. +pub const WEIGHT_PER_GAS: u64 = WEIGHT_PER_SECOND.ref_time() / GAS_PER_SECOND; + +pub struct FixedGasWeightMapping; + +impl GasWeightMapping for FixedGasWeightMapping { + fn gas_to_weight(gas: u64) -> Weight { + Weight::from_ref_time(gas.saturating_mul(WEIGHT_PER_GAS)) + } + fn weight_to_gas(weight: Weight) -> u64 { + weight.ref_time().wrapping_div(WEIGHT_PER_GAS) + } +} + +/// An ipmlementation of Frontier's AddressMapping trait for Sgx Accounts. +/// This is basically identical to Frontier's own IdentityAddressMapping, but it works for any type +/// that is Into like AccountId20 for example. +pub struct IntoAddressMapping; + +impl> AddressMapping for IntoAddressMapping { + fn into_account_id(address: H160) -> T { + address.into() + } +} + +parameter_types! { + pub const ChainId: u64 = 42; + pub BlockGasLimit: U256 = U256::from(NORMAL_DISPATCH_RATIO * MAXIMUM_BLOCK_WEIGHT.ref_time() / WEIGHT_PER_GAS); + //pub PrecompilesValue: FrontierPrecompiles = FrontierPrecompiles::<_>::new(); +} + +impl pallet_evm::Config for Runtime { + type FeeCalculator = FixedGasPrice; + type GasWeightMapping = FixedGasWeightMapping; + type BlockHashMapping = SubstrateBlockHashMapping; + type CallOrigin = EnsureAddressTruncated; + type WithdrawOrigin = EnsureAddressTruncated; + type AddressMapping = HashedAddressMapping; + type Currency = Balances; + type Event = Event; + type Runner = pallet_evm::runner::stack::Runner; + type PrecompilesType = (); + type PrecompilesValue = (); + type ChainId = ChainId; + type OnChargeTransaction = (); + type BlockGasLimit = BlockGasLimit; + type FindAuthor = (); // Currently not available. Would need some more thoughts how prioritisation fees could be handled. +} diff --git a/tee-worker/app-libs/sgx-runtime/src/lib.rs b/tee-worker/app-libs/sgx-runtime/src/lib.rs new file mode 100644 index 0000000000..055aad2c01 --- /dev/null +++ b/tee-worker/app-libs/sgx-runtime/src/lib.rs @@ -0,0 +1,332 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +//! The Substrate Node Template sgx-runtime for SGX. +//! This is only meant to be used inside an SGX enclave with `#[no_std]` +//! +//! you should assemble your sgx-runtime to be used with your STF here +//! and get all your needed pallets in + +#![cfg_attr(not(feature = "std"), no_std)] +#![feature(prelude_import)] +#![feature(structural_match)] +#![feature(core_intrinsics)] +#![feature(derive_eq)] +// `construct_runtime!` does a lot of recursion and requires us to increase the limit to 256. +#![recursion_limit = "256"] + +#[cfg(feature = "evm")] +mod evm; + +#[cfg(feature = "evm")] +pub use evm::{ + AddressMapping, EnsureAddressTruncated, EvmCall, FeeCalculator, FixedGasPrice, + FixedGasWeightMapping, GasWeightMapping, HashedAddressMapping, IntoAddressMapping, + SubstrateBlockHashMapping, GAS_PER_SECOND, MAXIMUM_BLOCK_WEIGHT, WEIGHT_PER_GAS, +}; +use frame_system::EnsureRoot; + +use core::convert::{TryFrom, TryInto}; +use frame_support::{traits::ConstU32, weights::ConstantMultiplier}; +use pallet_transaction_payment::CurrencyAdapter; +use sp_api::impl_runtime_apis; +use sp_core::OpaqueMetadata; +use sp_runtime::{ + create_runtime_str, generic, + traits::{AccountIdLookup, BlakeTwo256, Block as BlockT}, +}; +use sp_std::prelude::*; +use sp_version::RuntimeVersion; + +// Re-exports from itp-sgx-runtime-primitives. +pub use itp_sgx_runtime_primitives::{ + constants::SLOT_DURATION, + types::{ + AccountData, AccountId, Address, Balance, BlockNumber, Hash, Header, Index, Signature, + }, +}; + +// A few exports that help ease life for downstream crates. +pub use frame_support::{ + construct_runtime, parameter_types, + traits::{KeyOwnerProofSystem, Randomness}, + weights::{ + constants::{BlockExecutionWeight, ExtrinsicBaseWeight, RocksDbWeight, WEIGHT_PER_SECOND}, + IdentityFee, Weight, + }, + StorageValue, +}; +pub use pallet_balances::Call as BalancesCall; +pub use pallet_parentchain::{self, Call as ParentchainCall}; +pub use pallet_timestamp::Call as TimestampCall; +#[cfg(any(feature = "std", test))] +pub use sp_runtime::BuildStorage; +pub use sp_runtime::{Perbill, Permill}; + +// litentry +pub use pallet_identity_management::{self, Call as IdentityManagementCall}; + +/// Block type as expected by this sgx-runtime. +pub type Block = generic::Block; +/// A Block signed with a Justification +pub type SignedBlock = generic::SignedBlock; +/// BlockId type as expected by this sgx-runtime. +pub type BlockId = generic::BlockId; + +/// The SignedExtension to the basic transaction logic. +pub type SignedExtra = ( + frame_system::CheckNonZeroSender, + frame_system::CheckSpecVersion, + frame_system::CheckTxVersion, + frame_system::CheckGenesis, + frame_system::CheckEra, + frame_system::CheckNonce, + frame_system::CheckWeight, + pallet_transaction_payment::ChargeTransactionPayment, +); +/// Unchecked extrinsic type as expected by this sgx-runtime. +pub type UncheckedExtrinsic = generic::UncheckedExtrinsic; +/// Extrinsic type that has already been checked. +pub type CheckedExtrinsic = generic::CheckedExtrinsic; +/// Executive: handles dispatch to the various modules. +pub type Executive = frame_executive::Executive< + Runtime, + Block, + frame_system::ChainContext, + Runtime, + AllPalletsWithSystem, +>; + +/// Opaque types. These are used by the CLI to instantiate machinery that don't need to know +/// the specifics of the sgx-runtime. They can then be made to be agnostic over specific formats +/// of data like extrinsics, allowing for them to continue syncing the network through upgrades +/// to even the core data structures. +pub mod opaque { + + use sp_runtime::generic; + pub use sp_runtime::OpaqueExtrinsic as UncheckedExtrinsic; + + /// Opaque block header type. + pub type Header = itp_sgx_runtime_primitives::types::Header; + /// Opaque block type. + pub type Block = super::Block; + /// Opaque block identifier type. + pub type BlockId = generic::BlockId; +} + +pub const VERSION: RuntimeVersion = RuntimeVersion { + spec_name: create_runtime_str!("node-template"), + impl_name: create_runtime_str!("node-template"), + authoring_version: 1, + spec_version: 1, + impl_version: 1, + apis: RUNTIME_API_VERSIONS, + transaction_version: 1, + state_version: 0, +}; + +const NORMAL_DISPATCH_RATIO: Perbill = Perbill::from_percent(75); + +parameter_types! { + pub const Version: RuntimeVersion = VERSION; + pub const BlockHashCount: BlockNumber = 2400; + /// We allow for 2 seconds of compute with a 6 second average block time. + pub BlockWeights: frame_system::limits::BlockWeights = frame_system::limits::BlockWeights + ::with_sensible_defaults(WEIGHT_PER_SECOND.saturating_mul(2), NORMAL_DISPATCH_RATIO); + pub BlockLength: frame_system::limits::BlockLength = frame_system::limits::BlockLength + ::max_with_normal_ratio(5 * 1024 * 1024, NORMAL_DISPATCH_RATIO); + pub const SS58Prefix: u8 = 42; +} + +// Configure FRAME pallets to include in sgx-runtime. + +impl frame_system::Config for Runtime { + /// The basic call filter to use in dispatchable. + type BaseCallFilter = frame_support::traits::Everything; + /// Block & extrinsics weights: base values and limits. + type BlockWeights = BlockWeights; + /// The maximum length of a block (in bytes). + type BlockLength = BlockLength; + /// The identifier used to distinguish between accounts. + type AccountId = AccountId; + /// The aggregated dispatch type that is available for extrinsics. + type Call = Call; + /// The lookup mechanism to get account ID from whatever is passed in dispatchers. + type Lookup = AccountIdLookup; + /// The index type for storing how many extrinsics an account has signed. + type Index = Index; + /// The index type for blocks. + type BlockNumber = BlockNumber; + /// The type for hashing blocks and tries. + type Hash = Hash; + /// The hashing algorithm used. + type Hashing = BlakeTwo256; + /// The header type. + type Header = Header; + /// The ubiquitous event type. + type Event = Event; + /// The ubiquitous origin type. + type Origin = Origin; + /// Maximum number of block number to block hash mappings to keep (oldest pruned first). + type BlockHashCount = BlockHashCount; + /// The weight of database operations that the sgx-runtime can invoke. + type DbWeight = RocksDbWeight; + /// Version of the sgx-runtime. + type Version = Version; + /// Converts a module to the index of the module in `construct_runtime!`. + /// + /// This type is being generated by `construct_runtime!`. + type PalletInfo = PalletInfo; + /// What to do if a new account is created. + type OnNewAccount = (); + /// What to do if an account is fully reaped from the system. + type OnKilledAccount = (); + /// The data to be stored in an account. + type AccountData = AccountData; + /// Weight information for the extrinsics of this pallet. + type SystemWeightInfo = (); + /// This is used as an identifier of the chain. 42 is the generic substrate prefix. + type SS58Prefix = SS58Prefix; + /// The set code logic, just the default since we're not a parachain. + type OnSetCode = (); + /// The maximum number of consumers allowed on a single account. + type MaxConsumers = frame_support::traits::ConstU32<16>; +} + +parameter_types! { + pub const MinimumPeriod: u64 = SLOT_DURATION / 2; +} + +impl pallet_timestamp::Config for Runtime { + /// A timestamp: milliseconds since the unix epoch. + type Moment = u64; + type OnTimestampSet = (); + type MinimumPeriod = MinimumPeriod; + type WeightInfo = (); +} + +parameter_types! { + pub const ExistentialDeposit: u128 = 500; + pub const MaxLocks: u32 = 50; +} + +impl pallet_balances::Config for Runtime { + type MaxLocks = MaxLocks; + type MaxReserves = (); + type ReserveIdentifier = [u8; 8]; + /// The type for recording an account's balance. + type Balance = Balance; + /// The ubiquitous event type. + type Event = Event; + type DustRemoval = (); + type ExistentialDeposit = ExistentialDeposit; + type AccountStore = System; + type WeightInfo = (); +} + +parameter_types! { + pub const TransactionByteFee: Balance = 1; + pub const OperationalFeeMultiplier: u8 = 5; +} + +impl pallet_transaction_payment::Config for Runtime { + type Event = Event; + type OnChargeTransaction = CurrencyAdapter; + type OperationalFeeMultiplier = OperationalFeeMultiplier; + type WeightToFee = IdentityFee; + type LengthToFee = ConstantMultiplier; + type FeeMultiplierUpdate = (); +} + +impl pallet_sudo::Config for Runtime { + type Event = Event; + type Call = Call; +} + +impl pallet_parentchain::Config for Runtime { + type WeightInfo = (); +} + +impl pallet_identity_management::Config for Runtime { + type Event = Event; + type ManageOrigin = EnsureRoot; + type MaxMetadataLength = ConstU32<128>; + type MaxVerificationDelay = ConstU32<20>; +} + +// The plain sgx-runtime without the `evm-pallet` +#[cfg(not(feature = "evm"))] +construct_runtime!( + pub enum Runtime where + Block = Block, + NodeBlock = opaque::Block, + UncheckedExtrinsic = UncheckedExtrinsic + { + System: frame_system::{Pallet, Call, Config, Storage, Event}, + Timestamp: pallet_timestamp::{Pallet, Call, Storage, Inherent}, + Balances: pallet_balances::{Pallet, Call, Storage, Config, Event}, + TransactionPayment: pallet_transaction_payment::{Pallet, Storage, Event}, + Sudo: pallet_sudo::{Pallet, Call, Config, Storage, Event}, + Parentchain: pallet_parentchain::{Pallet, Call, Storage}, + IdentityManagement: pallet_identity_management, + } +); + +// Runtime constructed with the evm pallet. +// +// We need add the compiler-flag for the whole macro because it does not support +// compiler flags withing the macro. +#[cfg(feature = "evm")] +construct_runtime!( + pub enum Runtime where + Block = Block, + NodeBlock = opaque::Block, + UncheckedExtrinsic = UncheckedExtrinsic + { + System: frame_system::{Pallet, Call, Config, Storage, Event}, + Timestamp: pallet_timestamp::{Pallet, Call, Storage, Inherent}, + Balances: pallet_balances::{Pallet, Call, Storage, Config, Event}, + TransactionPayment: pallet_transaction_payment::{Pallet, Storage, Event}, + Sudo: pallet_sudo::{Pallet, Call, Config, Storage, Event}, + Parentchain: pallet_parentchain::{Pallet, Call, Storage}, + IdentityManagement: pallet_identity_management, + + Evm: pallet_evm::{Pallet, Call, Storage, Config, Event}, + } +); + +impl_runtime_apis! { + impl sp_api::Core for Runtime { + fn version() -> RuntimeVersion { + VERSION + } + + fn execute_block(block: Block) { + Executive::execute_block(block); + } + + fn initialize_block(header: &::Header) { + Executive::initialize_block(header) + } + } + + impl sp_api::Metadata for Runtime { + fn metadata() -> OpaqueMetadata { + OpaqueMetadata::new(Runtime::metadata().into()) + } + } + +} diff --git a/tee-worker/app-libs/stf/Cargo.toml b/tee-worker/app-libs/stf/Cargo.toml new file mode 100644 index 0000000000..856d8eda05 --- /dev/null +++ b/tee-worker/app-libs/stf/Cargo.toml @@ -0,0 +1,101 @@ +[package] +authors = ["Integritee AG "] +edition = "2021" +name = "ita-stf" +version = "0.9.0" + +[dependencies] +# crates.io +codec = { version = "3.0.0", default-features = false, features = ["derive"], package = "parity-scale-codec" } +derive_more = { version = "0.99.5" } +log = { version = "0.4", default-features = false } +rlp = { version = "0.5", default-features = false } +sha3 = { version = "0.10", default-features = false } + +# sgx deps +sgx_tstd = { branch = "master", features = ["untrusted_fs", "net", "backtrace"], git = "https://github.com/apache/teaclave-sgx-sdk.git", optional = true } + +# local crates +ita-sgx-runtime = { default-features = false, path = "../sgx-runtime" } +itp-hashing = { default-features = false, path = "../../core-primitives/hashing" } +itp-node-api = { path = "../../core-primitives/node-api", default-features = false } +itp-node-api-metadata = { path = "../../core-primitives/node-api/metadata", default-features = false } +itp-node-api-metadata-provider = { path = "../../core-primitives/node-api/metadata-provider", default-features = false } +itp-sgx-externalities = { default-features = false, path = "../../core-primitives/substrate-sgx/externalities" } +itp-stf-interface = { default-features = false, path = "../../core-primitives/stf-interface" } +itp-storage = { default-features = false, path = "../../core-primitives/storage" } +itp-types = { default-features = false, path = "../../core-primitives/types" } +itp-utils = { default-features = false, path = "../../core-primitives/utils" } +sp-io = { default-features = false, features = ["disable_oom", "disable_panic_handler", "disable_allocator"], path = "../../core-primitives/substrate-sgx/sp-io" } + +# Substrate dependencies +frame-support = { default-features = false, git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.29" } +frame-system = { default-features = false, git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.29" } +pallet-balances = { default-features = false, git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.29" } +pallet-sudo = { default-features = false, git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.29" } +sp-application-crypto = { default-features = false, features = ["full_crypto"], git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.29" } +sp-core = { default-features = false, features = ["full_crypto"], git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.29" } +sp-runtime = { default-features = false, git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.29" } + +# litentry +lc-stf-task-sender = { path = "../../litentry/core/stf-task/sender", default-features = false } +litentry-primitives = { path = "../../litentry/primitives", default-features = false } +pallet-parentchain = { default-features = false, git = "https://github.com/integritee-network/pallets.git", branch = "master" } +parentchain-primitives = { package = "primitives", git = "https://github.com/litentry/litentry-parachain.git", branch = "tee-dev", default-features = false, optional = true } +rand = { version = "0.7", optional = true } +rand-sgx = { package = "rand", git = "https://github.com/mesalock-linux/rand-sgx", tag = "sgx_1.1.3", features = ["sgx_tstd"], optional = true } +ring = { version = "0.16.20", default-features = false } + +[dev-dependencies] +sp-keyring = { git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.29" } + +[features] +default = ["std"] +evm = ["ita-sgx-runtime/evm"] +evm_std = [ + "evm", + "ita-sgx-runtime/evm_std", +] +mockserver = [] +sgx = [ + "sgx_tstd", + "itp-sgx-externalities/sgx", + "itp-utils/sgx", + "sp-io/sgx", + "itp-node-api/sgx", + "litentry-primitives/sgx", + "lc-stf-task-sender/sgx", + "rand-sgx", +] +std = [ + # crates.io + "codec/std", + "log/std", + "rlp/std", + # local + "ita-sgx-runtime/std", + "itp-hashing/std", + "itp-sgx-externalities/std", + "itp-stf-interface/std", + "itp-storage/std", + "itp-types/std", + "itp-utils/std", + # substrate + "sp-core/std", + "pallet-balances/std", + "pallet-sudo/std", + "frame-system/std", + "frame-support/std", + "sp-application-crypto/std", + "sp-runtime/std", + # litentry + "pallet-parentchain/std", + "sp-io/std", + "ita-sgx-runtime/std", + "itp-node-api/std", + "litentry-primitives/std", + "parentchain-primitives/std", + "lc-stf-task-sender/std", + "rand", +] +test = [] diff --git a/tee-worker/app-libs/stf/src/evm_helpers.rs b/tee-worker/app-libs/stf/src/evm_helpers.rs new file mode 100644 index 0000000000..5280ea5f0e --- /dev/null +++ b/tee-worker/app-libs/stf/src/evm_helpers.rs @@ -0,0 +1,67 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ +use crate::{ + helpers::{get_storage_double_map, get_storage_map}, + AccountId, Index, H256, +}; +use itp_storage::StorageHasher; +use sha3::{Digest, Keccak256}; +use sp_core::H160; +use std::prelude::v1::*; + +pub fn get_evm_account_codes(evm_account: &H160) -> Option> { + get_storage_map("Evm", "AccountCodes", evm_account, &StorageHasher::Blake2_128Concat) +} + +pub fn get_evm_account_storages(evm_account: &H160, index: &H256) -> Option { + get_storage_double_map( + "Evm", + "AccountStorages", + evm_account, + &StorageHasher::Blake2_128Concat, + index, + &StorageHasher::Blake2_128Concat, + ) +} + +// FIXME: Once events are available, these addresses should be read from events. +pub fn evm_create_address(caller: H160, nonce: Index) -> H160 { + let mut stream = rlp::RlpStream::new_list(2); + stream.append(&caller); + stream.append(&nonce); + H256::from_slice(Keccak256::digest(&stream.out()).as_slice()).into() +} + +// FIXME: Once events are available, these addresses should be read from events. +pub fn evm_create2_address(caller: H160, salt: H256, code_hash: H256) -> H160 { + let mut hasher = Keccak256::new(); + hasher.update([0xff]); + hasher.update(&caller[..]); + hasher.update(&salt[..]); + hasher.update(&code_hash[..]); + H256::from_slice(hasher.finalize().as_slice()).into() +} + +pub fn create_code_hash(code: &[u8]) -> H256 { + H256::from_slice(Keccak256::digest(code).as_slice()) +} + +pub fn get_evm_account(account: &AccountId) -> H160 { + let mut evm_acc_slice: [u8; 20] = [0; 20]; + evm_acc_slice.copy_from_slice((<[u8; 32]>::from(account.clone())).get(0..20).unwrap()); + evm_acc_slice.into() +} diff --git a/tee-worker/app-libs/stf/src/getter.rs b/tee-worker/app-libs/stf/src/getter.rs new file mode 100644 index 0000000000..d63ce73225 --- /dev/null +++ b/tee-worker/app-libs/stf/src/getter.rs @@ -0,0 +1,184 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +use crate::{AccountId, IdentityManagement, KeyPair, Signature}; +use codec::{Decode, Encode}; +use ita_sgx_runtime::System; +use itp_stf_interface::ExecuteGetter; +use itp_utils::stringify::account_id_to_string; +use log::*; +use sp_runtime::traits::Verify; +use std::prelude::v1::*; + +#[cfg(feature = "evm")] +use ita_sgx_runtime::{AddressMapping, HashedAddressMapping}; + +#[cfg(feature = "evm")] +use crate::evm_helpers::{get_evm_account, get_evm_account_codes, get_evm_account_storages}; + +#[cfg(feature = "evm")] +use sp_core::{H160, H256}; + +#[derive(Encode, Decode, Clone, Debug, PartialEq, Eq)] +#[allow(non_camel_case_types)] +pub enum Getter { + public(PublicGetter), + trusted(TrustedGetterSigned), +} + +impl From for Getter { + fn from(item: PublicGetter) -> Self { + Getter::public(item) + } +} + +impl From for Getter { + fn from(item: TrustedGetterSigned) -> Self { + Getter::trusted(item) + } +} + +#[derive(Encode, Decode, Clone, Debug, PartialEq, Eq)] +#[allow(non_camel_case_types)] +pub enum PublicGetter { + some_value, +} + +#[derive(Encode, Decode, Clone, Debug, PartialEq, Eq)] +#[allow(non_camel_case_types)] +pub enum TrustedGetter { + free_balance(AccountId), + reserved_balance(AccountId), + nonce(AccountId), + #[cfg(feature = "evm")] + evm_nonce(AccountId), + #[cfg(feature = "evm")] + evm_account_codes(AccountId, H160), + #[cfg(feature = "evm")] + evm_account_storages(AccountId, H160, H256), + // litentry + user_shielding_key(AccountId), +} + +impl TrustedGetter { + pub fn sender_account(&self) -> &AccountId { + match self { + TrustedGetter::free_balance(sender_account) => sender_account, + TrustedGetter::reserved_balance(sender_account) => sender_account, + TrustedGetter::nonce(sender_account) => sender_account, + #[cfg(feature = "evm")] + TrustedGetter::evm_nonce(sender_account) => sender_account, + #[cfg(feature = "evm")] + TrustedGetter::evm_account_codes(sender_account, _) => sender_account, + #[cfg(feature = "evm")] + TrustedGetter::evm_account_storages(sender_account, ..) => sender_account, + // litentry + TrustedGetter::user_shielding_key(account) => account, + } + } + + pub fn sign(&self, pair: &KeyPair) -> TrustedGetterSigned { + let signature = pair.sign(self.encode().as_slice()); + TrustedGetterSigned { getter: self.clone(), signature } + } +} + +#[derive(Encode, Decode, Clone, Debug, PartialEq, Eq)] +pub struct TrustedGetterSigned { + pub getter: TrustedGetter, + pub signature: Signature, +} + +impl TrustedGetterSigned { + pub fn new(getter: TrustedGetter, signature: Signature) -> Self { + TrustedGetterSigned { getter, signature } + } + + pub fn verify_signature(&self) -> bool { + self.signature + .verify(self.getter.encode().as_slice(), self.getter.sender_account()) + } +} + +impl ExecuteGetter for Getter { + fn execute(self) -> Option> { + match self { + Getter::trusted(g) => match &g.getter { + TrustedGetter::free_balance(who) => { + let info = System::account(&who); + debug!("TrustedGetter free_balance"); + debug!("AccountInfo for {} is {:?}", account_id_to_string(&who), info); + debug!("Account free balance is {}", info.data.free); + Some(info.data.free.encode()) + }, + + TrustedGetter::reserved_balance(who) => { + let info = System::account(&who); + debug!("TrustedGetter reserved_balance"); + debug!("AccountInfo for {} is {:?}", account_id_to_string(&who), info); + debug!("Account reserved balance is {}", info.data.reserved); + Some(info.data.reserved.encode()) + }, + TrustedGetter::nonce(who) => { + let nonce = System::account_nonce(&who); + debug!("TrustedGetter nonce"); + debug!("Account nonce is {}", nonce); + Some(nonce.encode()) + }, + #[cfg(feature = "evm")] + TrustedGetter::evm_nonce(who) => { + let evm_account = get_evm_account(who); + let evm_account = HashedAddressMapping::into_account_id(evm_account); + let nonce = System::account_nonce(&evm_account); + debug!("TrustedGetter evm_nonce"); + debug!("Account nonce is {}", nonce); + Some(nonce.encode()) + }, + #[cfg(feature = "evm")] + TrustedGetter::evm_account_codes(_who, evm_account) => + // TODO: This probably needs some security check if who == evm_account (or assosciated) + if let Some(info) = get_evm_account_codes(evm_account) { + debug!("TrustedGetter Evm Account Codes"); + debug!("AccountCodes for {} is {:?}", evm_account, info); + Some(info) // TOOD: encoded? + } else { + None + }, + #[cfg(feature = "evm")] + TrustedGetter::evm_account_storages(_who, evm_account, index) => + // TODO: This probably needs some security check if who == evm_account (or assosciated) + if let Some(value) = get_evm_account_storages(evm_account, index) { + debug!("TrustedGetter Evm Account Storages"); + debug!("AccountStorages for {} is {:?}", evm_account, value); + Some(value.encode()) + } else { + None + }, + // litentry + TrustedGetter::user_shielding_key(who) => + IdentityManagement::user_shielding_keys(&who).map(|key| key.encode()), + }, + Getter::public(g) => match g { + PublicGetter::some_value => Some(42u32.encode()), + }, + } + } + + fn get_storage_hashes_to_update(&self) -> Vec> { + Vec::new() + } +} diff --git a/tee-worker/app-libs/stf/src/hash.rs b/tee-worker/app-libs/stf/src/hash.rs new file mode 100644 index 0000000000..b458f8094d --- /dev/null +++ b/tee-worker/app-libs/stf/src/hash.rs @@ -0,0 +1,56 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +pub use itp_hashing::Hash; + +use crate::{TrustedGetter, TrustedOperation}; +use codec::{Decode, Encode}; +use itp_types::H256; +use sp_core::blake2_256; +use std::{boxed::Box, vec::Vec}; + +/// Trusted operation Or hash +/// +/// Allows to refer to trusted calls either by its raw representation or its hash. +#[derive(Clone, Debug, Encode, Decode, PartialEq)] +#[allow(clippy::large_enum_variant)] +pub enum TrustedOperationOrHash { + /// The hash of the call. + Hash(Hash), + /// Raw extrinsic bytes. + OperationEncoded(Vec), + /// Raw extrinsic + Operation(Box), +} + +impl TrustedOperationOrHash { + pub fn from_top(top: TrustedOperation) -> Self { + TrustedOperationOrHash::Operation(Box::new(top)) + } +} + +impl Hash for TrustedOperation { + fn hash(&self) -> H256 { + blake2_256(&self.encode()).into() + } +} + +impl Hash for TrustedGetter { + fn hash(&self) -> H256 { + blake2_256(&self.encode()).into() + } +} diff --git a/tee-worker/app-libs/stf/src/helpers.rs b/tee-worker/app-libs/stf/src/helpers.rs new file mode 100644 index 0000000000..4eb6b2d36c --- /dev/null +++ b/tee-worker/app-libs/stf/src/helpers.rs @@ -0,0 +1,173 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +use crate::{StfError, StfResult, ENCLAVE_ACCOUNT_KEY}; + +use ring::{ + aead::{Aad, BoundKey, Nonce, NonceSequence, SealingKey, UnboundKey, AES_256_GCM}, + error::Unspecified, +}; + +use codec::{Decode, Encode}; +use itp_storage::{storage_double_map_key, storage_map_key, storage_value_key, StorageHasher}; +use itp_utils::stringify::account_id_to_string; +use litentry_primitives::{ + AesOutput, ChallengeCode, UserShieldingKeyType, USER_SHIELDING_KEY_NONCE_LEN, +}; +use log::*; +use std::prelude::v1::*; + +#[cfg(all(not(feature = "std"), feature = "sgx"))] +extern crate rand_sgx as rand; + +use rand::Rng; + +pub fn get_storage_value( + storage_prefix: &'static str, + storage_key_name: &'static str, +) -> Option { + let key = storage_value_key(storage_prefix, storage_key_name); + get_storage_by_key_hash(key) +} + +pub fn get_storage_map( + storage_prefix: &'static str, + storage_key_name: &'static str, + map_key: &K, + hasher: &StorageHasher, +) -> Option { + let key = storage_map_key::(storage_prefix, storage_key_name, map_key, hasher); + get_storage_by_key_hash(key) +} + +pub fn get_storage_double_map( + storage_prefix: &'static str, + storage_key_name: &'static str, + first: &K, + first_hasher: &StorageHasher, + second: &Q, + second_hasher: &StorageHasher, +) -> Option { + let key = storage_double_map_key::( + storage_prefix, + storage_key_name, + first, + first_hasher, + second, + second_hasher, + ); + get_storage_by_key_hash(key) +} + +/// Get value in storage. +pub fn get_storage_by_key_hash(key: Vec) -> Option { + if let Some(value_encoded) = sp_io::storage::get(&key) { + if let Ok(value) = Decode::decode(&mut value_encoded.as_slice()) { + Some(value) + } else { + error!("could not decode state for key {:x?}", key); + None + } + } else { + info!("key not found in state {:x?}", key); + None + } +} + +/// Get the AccountInfo key where the account is stored. +pub fn account_key_hash(account: &AccountId) -> Vec { + storage_map_key("System", "Account", account, &StorageHasher::Blake2_128Concat) +} + +pub fn enclave_signer_account() -> AccountId { + get_storage_value("Sudo", ENCLAVE_ACCOUNT_KEY).expect("No enclave account") +} + +/// Ensures an account is a registered enclave account. +pub fn ensure_enclave_signer_account( + account: &AccountId, +) -> StfResult<()> { + let expected_enclave_account: AccountId = enclave_signer_account(); + if &expected_enclave_account == account { + Ok(()) + } else { + error!( + "Expected enclave account {}, but found {}", + account_id_to_string(&expected_enclave_account), + account_id_to_string(account) + ); + Err(StfError::RequireEnclaveSignerAccount) + } +} + +pub fn set_block_number(block_number: u32) { + sp_io::storage::set(&storage_value_key("System", "Number"), &block_number.encode()); +} + +// Litentry +pub fn aes_encrypt_default(key: &UserShieldingKeyType, data: &[u8]) -> AesOutput { + let mut in_out = data.to_vec(); + + let nonce = RingAeadNonceSequence::new(); + let aad = b""; + let unbound_key = UnboundKey::new(&AES_256_GCM, key.as_slice()).unwrap(); + let mut sealing_key = SealingKey::new(unbound_key, nonce.clone()); + sealing_key.seal_in_place_append_tag(Aad::from(aad), &mut in_out).unwrap(); + + AesOutput { ciphertext: in_out.to_vec(), aad: aad.to_vec(), nonce: nonce.nonce } +} + +#[cfg(feature = "mockserver")] +pub fn generate_challenge_code() -> ChallengeCode { + // Hard Code ChallengeCode for mockserver test + // rand::thread_rng().gen::() + // hex: 0x08685a3823d512fad5d277f102ae1808 + let code: ChallengeCode = [8, 104, 90, 56, 35, 213, 18, 250, 213, 210, 119, 241, 2, 174, 24, 8]; + code +} + +#[cfg(not(feature = "mockserver"))] +pub fn generate_challenge_code() -> ChallengeCode { + rand::thread_rng().gen::() +} + +#[derive(Clone)] +pub struct RingAeadNonceSequence { + pub nonce: [u8; USER_SHIELDING_KEY_NONCE_LEN], +} + +impl RingAeadNonceSequence { + fn new() -> RingAeadNonceSequence { + RingAeadNonceSequence { nonce: [0u8; USER_SHIELDING_KEY_NONCE_LEN] } + } +} + +impl NonceSequence for RingAeadNonceSequence { + fn advance(&mut self) -> Result { + let nonce = Nonce::assume_unique_for_key(self.nonce); + + // FIXME: in function `ring::rand::sysrand::fill': undefined reference to `syscall' + // let mut nonce_vec = vec![0; USER_SHIELDING_KEY_NONCE_LEN]; + // let rand = SystemRandom::new(); + // rand.fill(&mut nonce_vec).unwrap(); + let nonce_vec = rand::thread_rng().gen::<[u8; USER_SHIELDING_KEY_NONCE_LEN]>(); + + self.nonce.copy_from_slice(&nonce_vec[0..USER_SHIELDING_KEY_NONCE_LEN]); + + Ok(nonce) + } +} diff --git a/tee-worker/app-libs/stf/src/lib.rs b/tee-worker/app-libs/stf/src/lib.rs new file mode 100644 index 0000000000..c8c16dd350 --- /dev/null +++ b/tee-worker/app-libs/stf/src/lib.rs @@ -0,0 +1,175 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +///////////////////////////////////////////////////////////////////////////// +#![feature(structural_match)] +#![feature(rustc_attrs)] +#![feature(core_intrinsics)] +#![feature(derive_eq)] +#![cfg_attr(all(not(target_env = "sgx"), not(feature = "std")), no_std)] +#![cfg_attr(target_env = "sgx", feature(rustc_private))] + +#[cfg(all(not(feature = "std"), feature = "sgx"))] +extern crate sgx_tstd as std; + +#[cfg(feature = "sgx")] +pub use ita_sgx_runtime::{Balance, BlockNumber, Index}; +#[cfg(feature = "std")] +pub use parentchain_primitives::{Balance, BlockNumber, Index}; + +use codec::{Compact, Decode, Encode}; +use derive_more::Display; +use ita_sgx_runtime::{ + pallet_identity_management::MetadataOf, IdentityManagement, Runtime, System, +}; +use itp_node_api_metadata::Error as MetadataError; +use itp_node_api_metadata_provider::Error as MetadataProviderError; +use sp_core::{crypto::AccountId32, ed25519, sr25519, Pair, H256}; +use sp_runtime::{traits::Verify, MultiSignature}; +use std::{boxed::Box, string::String}; + +pub use getter::*; +pub use stf_sgx_primitives::{types::*, Stf}; +pub use trusted_call::*; + +#[cfg(feature = "evm")] +pub mod evm_helpers; +pub mod getter; +pub mod hash; +pub mod helpers; +pub mod stf_sgx; +pub mod stf_sgx_primitives; +#[cfg(all(feature = "test", feature = "sgx"))] +pub mod stf_sgx_tests; +#[cfg(all(feature = "test", feature = "sgx"))] +pub mod test_genesis; +pub mod trusted_call; +pub mod trusted_call_litentry; + +pub(crate) const ENCLAVE_ACCOUNT_KEY: &str = "Enclave_Account_Key"; + +pub type Signature = MultiSignature; +pub type AuthorityId = ::Signer; +pub type AccountId = AccountId32; +pub type Hash = H256; +pub type BalanceTransferFn = ([u8; 2], AccountId, Compact); + +pub type ShardIdentifier = H256; + +pub type StfResult = Result; + +#[derive(Debug, Display, PartialEq, Eq)] +pub enum StfError { + #[display(fmt = "Insufficient privileges {:?}, are you sure you are root?", _0)] + MissingPrivileges(AccountId), + #[display(fmt = "Valid enclave signer account is required")] + RequireEnclaveSignerAccount, + #[display(fmt = "Error dispatching runtime call. {:?}", _0)] + Dispatch(String), + #[display(fmt = "Not enough funds to perform operation")] + MissingFunds, + #[display(fmt = "Invalid Nonce {:?}", _0)] + InvalidNonce(Index), + StorageHashMismatch, + InvalidStorageDiff, + // litentry + LayerOneNumberUnavailable, + InvalidMetadata, + #[display(fmt = "Identity verification failed")] + VerifyIdentityFailed, + AssertionBuildFail, +} + +impl From for StfError { + fn from(_e: MetadataError) -> Self { + StfError::InvalidMetadata + } +} + +impl From for StfError { + fn from(_e: MetadataProviderError) -> Self { + StfError::InvalidMetadata + } +} +#[derive(Clone)] +pub enum KeyPair { + Sr25519(Box), + Ed25519(Box), +} + +impl KeyPair { + fn sign(&self, payload: &[u8]) -> Signature { + match self { + Self::Sr25519(pair) => pair.sign(payload).into(), + Self::Ed25519(pair) => pair.sign(payload).into(), + } + } +} + +impl From for KeyPair { + fn from(x: ed25519::Pair) -> Self { + KeyPair::Ed25519(Box::new(x)) + } +} + +impl From for KeyPair { + fn from(x: sr25519::Pair) -> Self { + KeyPair::Sr25519(Box::new(x)) + } +} + +#[derive(Encode, Decode, Clone, Debug, PartialEq, Eq)] +#[allow(non_camel_case_types)] +pub enum TrustedOperation { + indirect_call(TrustedCallSigned), + direct_call(TrustedCallSigned), + get(Getter), +} + +impl From for TrustedOperation { + fn from(item: TrustedCallSigned) -> Self { + TrustedOperation::indirect_call(item) + } +} + +impl From for TrustedOperation { + fn from(item: Getter) -> Self { + TrustedOperation::get(item) + } +} + +impl From for TrustedOperation { + fn from(item: TrustedGetterSigned) -> Self { + TrustedOperation::get(item.into()) + } +} + +impl From for TrustedOperation { + fn from(item: PublicGetter) -> Self { + TrustedOperation::get(item.into()) + } +} + +impl TrustedOperation { + pub fn to_call(&self) -> Option<&TrustedCallSigned> { + match self { + TrustedOperation::direct_call(c) => Some(c), + TrustedOperation::indirect_call(c) => Some(c), + _ => None, + } + } +} diff --git a/tee-worker/app-libs/stf/src/stf_sgx.rs b/tee-worker/app-libs/stf/src/stf_sgx.rs new file mode 100644 index 0000000000..4c4ade8f13 --- /dev/null +++ b/tee-worker/app-libs/stf/src/stf_sgx.rs @@ -0,0 +1,279 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +#[cfg(feature = "test")] +use crate::test_genesis::test_genesis_setup; + +use crate::{helpers::enclave_signer_account, ShardIdentifier, Stf, StfError, ENCLAVE_ACCOUNT_KEY}; +use codec::Encode; +use frame_support::traits::{OriginTrait, UnfilteredDispatchable}; +use itp_node_api::metadata::{ + pallet_imp::IMPCallIndexes, pallet_teerex::TeerexCallIndexes, provider::AccessNodeMetadata, +}; +use itp_sgx_externalities::SgxExternalitiesTrait; +use itp_stf_interface::{ + parentchain_pallet::ParentchainPalletInterface, + sudo_pallet::SudoPalletInterface, + system_pallet::{SystemPalletAccountInterface, SystemPalletEventInterface}, + ExecuteCall, ExecuteGetter, InitState, StateCallInterface, StateGetterInterface, UpdateState, +}; +use itp_storage::storage_value_key; +use itp_types::OpaqueCall; +use itp_utils::stringify::account_id_to_string; +use log::*; +use sp_runtime::traits::StaticLookup; +use std::{fmt::Debug, format, prelude::v1::*, sync::Arc, vec}; + +impl InitState + for Stf +where + State: SgxExternalitiesTrait + Debug, + ::SgxExternalitiesType: core::default::Default, + Runtime: frame_system::Config + pallet_balances::Config, + <::Lookup as StaticLookup>::Source: + std::convert::From, + AccountId: Encode, +{ + fn init_state(enclave_account: AccountId) -> State { + debug!("initializing stf state, account id {}", account_id_to_string(&enclave_account)); + let mut state = State::new(Default::default()); + + state.execute_with(|| { + // Do not set genesis for pallets that are meant to be on-chain + // use get_storage_hashes_to_update instead. + + sp_io::storage::set(&storage_value_key("Balances", "TotalIssuance"), &11u128.encode()); + sp_io::storage::set(&storage_value_key("Balances", "CreationFee"), &1u128.encode()); + sp_io::storage::set(&storage_value_key("Balances", "TransferFee"), &1u128.encode()); + sp_io::storage::set( + &storage_value_key("Balances", "TransactionBaseFee"), + &1u128.encode(), + ); + sp_io::storage::set( + &storage_value_key("Balances", "TransactionByteFee"), + &1u128.encode(), + ); + sp_io::storage::set( + &storage_value_key("Balances", "ExistentialDeposit"), + &1u128.encode(), + ); + }); + + #[cfg(feature = "test")] + test_genesis_setup(&mut state); + + state.execute_with(|| { + sp_io::storage::set( + &storage_value_key("Sudo", ENCLAVE_ACCOUNT_KEY), + &enclave_account.encode(), + ); + + if let Err(e) = create_enclave_self_account::(enclave_account) { + error!("Failed to initialize the enclave signer account: {:?}", e); + } + }); + + trace!("Returning updated state: {:?}", state); + state + } +} + +impl + UpdateState::SgxExternalitiesDiffType> + for Stf +where + State: SgxExternalitiesTrait + Debug, + ::SgxExternalitiesType: core::default::Default, + ::SgxExternalitiesDiffType: + IntoIterator, Option>)>, +{ + fn apply_state_diff( + state: &mut State, + map_update: ::SgxExternalitiesDiffType, + ) { + state.execute_with(|| { + map_update.into_iter().for_each(|(k, v)| { + match v { + Some(value) => sp_io::storage::set(&k, &value), + None => sp_io::storage::clear(&k), + }; + }); + }); + } + + fn storage_hashes_to_update_on_block() -> Vec> { + // Get all shards that are currently registered. + vec![shards_key_hash()] + } +} + +impl + StateCallInterface for Stf +where + Call: ExecuteCall, + State: SgxExternalitiesTrait + Debug, + NodeMetadataRepository: AccessNodeMetadata, + NodeMetadataRepository::MetadataType: TeerexCallIndexes + IMPCallIndexes, +{ + type Error = Call::Error; + + fn execute_call( + state: &mut State, + shard: &ShardIdentifier, + call: Call, + calls: &mut Vec, + node_metadata_repo: Arc, + ) -> Result<(), Self::Error> { + state.execute_with(|| call.execute(shard, calls, node_metadata_repo)) + } +} + +impl StateGetterInterface + for Stf +where + Getter: ExecuteGetter, + State: SgxExternalitiesTrait + Debug, +{ + fn execute_getter(state: &mut State, getter: Getter) -> Option> { + state.execute_with(|| getter.execute()) + } +} + +impl SudoPalletInterface for Stf +where + State: SgxExternalitiesTrait, + Runtime: frame_system::Config + pallet_sudo::Config, +{ + type AccountId = Runtime::AccountId; + + fn get_root(state: &mut State) -> Self::AccountId { + state.execute_with(|| pallet_sudo::Pallet::::key().expect("No root account")) + } + + fn get_enclave_account(state: &mut State) -> Self::AccountId { + state.execute_with(enclave_signer_account::) + } +} + +impl SystemPalletAccountInterface + for Stf +where + State: SgxExternalitiesTrait, + Runtime: frame_system::Config, + AccountId: Encode, +{ + type Index = Runtime::Index; + type AccountData = Runtime::AccountData; + + fn get_account_nonce(state: &mut State, account: &AccountId) -> Self::Index { + state.execute_with(|| { + let nonce = frame_system::Pallet::::account_nonce(account); + debug!("Account {} nonce is {:?}", account_id_to_string(account), nonce); + nonce + }) + } + + fn get_account_data(state: &mut State, account: &AccountId) -> Self::AccountData { + state.execute_with(|| frame_system::Pallet::::account(account).data) + } +} + +impl SystemPalletEventInterface + for Stf +where + State: SgxExternalitiesTrait, + Runtime: frame_system::Config, +{ + type EventRecord = frame_system::EventRecord; + type EventIndex = u32; // For some reason this is not a pub type in frame_system + type BlockNumber = Runtime::BlockNumber; + type Hash = Runtime::Hash; + + fn get_events(state: &mut State) -> Vec> { + state.execute_with(|| frame_system::Pallet::::read_events_no_consensus()) + } + + fn get_event_count(state: &mut State) -> Self::EventIndex { + state.execute_with(|| frame_system::Pallet::::event_count()) + } + + fn get_event_topics( + state: &mut State, + topic: &Self::Hash, + ) -> Vec<(Self::BlockNumber, Self::EventIndex)> { + state.execute_with(|| frame_system::Pallet::::event_topics(topic)) + } + + fn reset_events(state: &mut State) { + state.execute_with(|| frame_system::Pallet::::reset_events()) + } +} + +impl + ParentchainPalletInterface for Stf +where + State: SgxExternalitiesTrait, + Runtime: frame_system::Config
+ pallet_parentchain::Config, +{ + type Error = StfError; + + fn update_parentchain_block( + state: &mut State, + header: ParentchainHeader, + ) -> Result<(), Self::Error> { + state.execute_with(|| { + pallet_parentchain::Call::::set_block { header } + .dispatch_bypass_filter(Runtime::Origin::root()) + .map_err(|e| { + Self::Error::Dispatch(format!("Update parentchain block error: {:?}", e.error)) + }) + })?; + Ok(()) + } +} + +pub fn storage_hashes_to_update_per_shard(_shard: &ShardIdentifier) -> Vec> { + Vec::new() +} + +pub fn shards_key_hash() -> Vec { + // here you have to point to a storage value containing a Vec of + // ShardIdentifiers the enclave uses this to autosubscribe to no shards + vec![] +} + +/// Creates valid enclave account with a balance that is above the existential deposit. +/// !! Requires a root to be set. +fn create_enclave_self_account( + enclave_account: AccountId, +) -> Result<(), StfError> +where + Runtime: frame_system::Config + pallet_balances::Config, + <::Lookup as StaticLookup>::Source: From, + Runtime::Balance: From, +{ + pallet_balances::Call::::set_balance { + who: enclave_account.into(), + new_free: 1000.into(), + new_reserved: 0.into(), + } + .dispatch_bypass_filter(Runtime::Origin::root()) + .map_err(|e| { + StfError::Dispatch(format!("Set Balance for enclave signer account error: {:?}", e.error)) + }) + .map(|_| ()) +} diff --git a/tee-worker/app-libs/stf/src/stf_sgx_primitives.rs b/tee-worker/app-libs/stf/src/stf_sgx_primitives.rs new file mode 100644 index 0000000000..cbbd13c7dc --- /dev/null +++ b/tee-worker/app-libs/stf/src/stf_sgx_primitives.rs @@ -0,0 +1,69 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +use codec::{Decode, Encode}; +use itp_types::H256; +use std::marker::PhantomData; + +pub mod types { + pub use itp_types::{AccountData, AccountInfo, BlockNumber, Header as ParentchainHeader}; + + pub type State = itp_sgx_externalities::SgxExternalities; + pub type StateType = itp_sgx_externalities::SgxExternalitiesType; + pub type StateDiffType = itp_sgx_externalities::SgxExternalitiesDiffType; + pub use super::StatePayload; +} + +pub struct Stf { + phantom_data: PhantomData<(Call, Getter, State, Runtime)>, +} + +/// Payload to be sent to peers for a state update. +#[derive(PartialEq, Eq, Clone, Debug, Encode, Decode)] +pub struct StatePayload { + /// State hash before the `state_update` was applied. + state_hash_apriori: H256, + /// State hash after the `state_update` was applied. + state_hash_aposteriori: H256, + /// State diff applied to state with hash `state_hash_apriori` + /// leading to state with hash `state_hash_aposteriori`. + state_update: StateUpdate, +} + +impl StatePayload { + /// Get state hash before the `state_update` was applied. + pub fn state_hash_apriori(&self) -> H256 { + self.state_hash_apriori + } + /// Get state hash after the `state_update` was applied. + pub fn state_hash_aposteriori(&self) -> H256 { + self.state_hash_aposteriori + } + /// Reference to the `state_update`. + pub fn state_update(&self) -> &StateUpdate { + &self.state_update + } + + /// Create new `StatePayload` instance. + pub fn new(apriori: H256, aposteriori: H256, update: StateUpdate) -> Self { + Self { + state_hash_apriori: apriori, + state_hash_aposteriori: aposteriori, + state_update: update, + } + } +} diff --git a/tee-worker/app-libs/stf/src/stf_sgx_tests.rs b/tee-worker/app-libs/stf/src/stf_sgx_tests.rs new file mode 100644 index 0000000000..b305bcafe2 --- /dev/null +++ b/tee-worker/app-libs/stf/src/stf_sgx_tests.rs @@ -0,0 +1,75 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +use crate::{ + AccountId, Getter, ShardIdentifier, Signature, State, Stf, TrustedCall, TrustedCallSigned, +}; +use ita_sgx_runtime::Runtime; +use itp_node_api::metadata::{metadata_mocks::NodeMetadataMock, provider::NodeMetadataRepository}; +use itp_stf_interface::{ + sudo_pallet::SudoPalletInterface, system_pallet::SystemPalletAccountInterface, InitState, + StateCallInterface, +}; + +use sp_core::{ + ed25519::{Pair as Ed25519Pair, Signature as Ed25519Signature}, + Pair, +}; +use std::{sync::Arc, vec::Vec}; + +pub type StfState = Stf; + +pub fn enclave_account_initialization_works() { + let enclave_account = AccountId::new([2u8; 32]); + let mut state = StfState::init_state(enclave_account.clone()); + let _root = StfState::get_root(&mut state); + let account_data = StfState::get_account_data(&mut state, &enclave_account); + + assert_eq!(0, StfState::get_account_nonce(&mut state, &enclave_account)); + assert_eq!(enclave_account, StfState::get_enclave_account(&mut state)); + assert_eq!(1000, account_data.free); +} + +pub fn shield_funds_increments_signer_account_nonce() { + let enclave_call_signer = Ed25519Pair::from_seed(b"14672678901234567890123456789012"); + let enclave_signer_account_id: AccountId = enclave_call_signer.public().into(); + let mut state = StfState::init_state(enclave_signer_account_id.clone()); + + let shield_funds_call = TrustedCallSigned::new( + TrustedCall::balance_shield( + enclave_call_signer.public().into(), + AccountId::new([1u8; 32]), + 500u128, + ), + 0, + Signature::Ed25519(Ed25519Signature([0u8; 64])), + ); + + let repo = Arc::new(NodeMetadataRepository::::default()); + let shard = ShardIdentifier::default(); + StfState::execute_call(&mut state, &shard, shield_funds_call, &mut Vec::new(), repo).unwrap(); + assert_eq!(1, StfState::get_account_nonce(&mut state, &enclave_signer_account_id)); +} + +pub fn test_root_account_exists_after_initialization() { + let enclave_account = AccountId::new([2u8; 32]); + let mut state = StfState::init_state(enclave_account); + let root_account = StfState::get_root(&mut state); + + let account_data = StfState::get_account_data(&mut state, &root_account); + assert!(account_data.free > 0); +} diff --git a/tee-worker/app-libs/stf/src/test_genesis.rs b/tee-worker/app-libs/stf/src/test_genesis.rs new file mode 100644 index 0000000000..b594b616f3 --- /dev/null +++ b/tee-worker/app-libs/stf/src/test_genesis.rs @@ -0,0 +1,121 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +use crate::StfError; +use frame_support::traits::UnfilteredDispatchable; +use ita_sgx_runtime::{Balance, Runtime, System}; +use itp_sgx_externalities::SgxExternalitiesTrait; +use itp_storage::storage_value_key; +use log::*; +use sgx_tstd as std; +use sp_core::{crypto::AccountId32, ed25519, Pair}; +use sp_runtime::MultiAddress; +use std::{format, vec, vec::Vec}; + +#[cfg(feature = "evm")] +use ita_sgx_runtime::{AddressMapping, HashedAddressMapping}; + +#[cfg(feature = "evm")] +use crate::evm_helpers::get_evm_account; + +type Seed = [u8; 32]; + +const ALICE_ENCODED: Seed = [ + 212, 53, 147, 199, 21, 253, 211, 28, 97, 20, 26, 189, 4, 169, 159, 214, 130, 44, 133, 88, 133, + 76, 205, 227, 154, 86, 132, 231, 165, 109, 162, 125, +]; + +const ENDOWED_SEED: Seed = *b"12345678901234567890123456789012"; +const SECOND_ENDOWED_SEED: Seed = *b"22345678901234567890123456789012"; +const UNENDOWED_SEED: Seed = *b"92345678901234567890123456789012"; + +const ALICE_FUNDS: Balance = 1000000000000000; +pub const ENDOWED_ACC_FUNDS: Balance = 2000; +pub const SECOND_ENDOWED_ACC_FUNDS: Balance = 1000; + +pub fn endowed_account() -> ed25519::Pair { + ed25519::Pair::from_seed(&ENDOWED_SEED) +} +pub fn second_endowed_account() -> ed25519::Pair { + ed25519::Pair::from_seed(&SECOND_ENDOWED_SEED) +} + +pub fn unendowed_account() -> ed25519::Pair { + ed25519::Pair::from_seed(&UNENDOWED_SEED) +} + +pub fn test_genesis_setup(state: &mut impl SgxExternalitiesTrait) { + // set alice sudo account + set_sudo_account(state, &ALICE_ENCODED); + trace!("Set new sudo account: {:?}", &ALICE_ENCODED); + + let mut endowees: Vec<(AccountId32, Balance, Balance)> = vec![ + (endowed_account().public().into(), ENDOWED_ACC_FUNDS, ENDOWED_ACC_FUNDS), + ( + second_endowed_account().public().into(), + SECOND_ENDOWED_ACC_FUNDS, + SECOND_ENDOWED_ACC_FUNDS, + ), + (ALICE_ENCODED.into(), ALICE_FUNDS, ALICE_FUNDS), + ]; + + append_funded_alice_evm_account(&mut endowees); + + endow(state, endowees); +} + +#[cfg(feature = "evm")] +fn append_funded_alice_evm_account(endowees: &mut Vec<(AccountId32, Balance, Balance)>) { + let alice_evm = get_evm_account(&ALICE_ENCODED.into()); + let alice_evm_substrate_version = HashedAddressMapping::into_account_id(alice_evm); + let mut other: Vec<(AccountId32, Balance, Balance)> = + vec![(alice_evm_substrate_version, ALICE_FUNDS, ALICE_FUNDS)]; + endowees.append(other.as_mut()); +} + +#[cfg(not(feature = "evm"))] +fn append_funded_alice_evm_account(_: &mut Vec<(AccountId32, Balance, Balance)>) {} + +fn set_sudo_account(state: &mut impl SgxExternalitiesTrait, account_encoded: &[u8]) { + state.execute_with(|| { + sp_io::storage::set(&storage_value_key("Sudo", "Key"), account_encoded); + }) +} + +pub fn endow( + state: &mut impl SgxExternalitiesTrait, + endowees: impl IntoIterator, +) { + state.execute_with(|| { + for e in endowees.into_iter() { + let account = e.0; + + ita_sgx_runtime::BalancesCall::::set_balance { + who: MultiAddress::Id(account.clone()), + new_free: e.1, + new_reserved: e.2, + } + .dispatch_bypass_filter(ita_sgx_runtime::Origin::root()) + .map_err(|e| StfError::Dispatch(format!("Balance Set Balance error: {:?}", e.error))) + .unwrap(); + + let print_public: [u8; 32] = account.clone().into(); + let account_info = System::account(&&print_public.into()); + debug!("{:?} balance is {}", print_public, account_info.data.free); + } + }); +} diff --git a/tee-worker/app-libs/stf/src/trusted_call.rs b/tee-worker/app-libs/stf/src/trusted_call.rs new file mode 100644 index 0000000000..8c6b939a4e --- /dev/null +++ b/tee-worker/app-libs/stf/src/trusted_call.rs @@ -0,0 +1,661 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +#[cfg(feature = "evm")] +use sp_core::{H160, H256, U256}; + +#[cfg(feature = "evm")] +use std::vec::Vec; + +use crate::{ + helpers::{aes_encrypt_default, ensure_enclave_signer_account}, + AccountId, IdentityManagement, KeyPair, MetadataOf, Runtime, ShardIdentifier, Signature, + StfError, System, TrustedOperation, +}; +use codec::{Decode, Encode}; +use frame_support::{ensure, traits::UnfilteredDispatchable}; +pub use ita_sgx_runtime::{Balance, Index}; +use itp_node_api::metadata::{ + pallet_imp::IMPCallIndexes, pallet_teerex::TeerexCallIndexes, provider::AccessNodeMetadata, +}; +use itp_stf_interface::ExecuteCall; +use itp_types::OpaqueCall; +use itp_utils::stringify::account_id_to_string; +use litentry_primitives::{ + ChallengeCode, Identity, ParentchainBlockNumber, UserShieldingKeyType, ValidationData, +}; +use log::*; +use sp_io::hashing::blake2_256; +use sp_runtime::{traits::Verify, MultiAddress}; +use std::{format, prelude::v1::*, sync::Arc}; + +#[cfg(feature = "evm")] +use ita_sgx_runtime::{AddressMapping, HashedAddressMapping}; + +#[cfg(feature = "evm")] +use crate::evm_helpers::{create_code_hash, evm_create2_address, evm_create_address}; + +#[derive(Encode, Decode, Clone, Debug, PartialEq, Eq)] +#[allow(non_camel_case_types)] +pub enum TrustedCall { + balance_set_balance(AccountId, AccountId, Balance, Balance), + balance_transfer(AccountId, AccountId, Balance), + balance_unshield(AccountId, AccountId, Balance, ShardIdentifier), // (AccountIncognito, BeneficiaryPublicAccount, Amount, Shard) + balance_shield(AccountId, AccountId, Balance), // (Root, AccountIncognito, Amount) + #[cfg(feature = "evm")] + evm_withdraw(AccountId, H160, Balance), // (Origin, Address EVM Account, Value) + // (Origin, Source, Target, Input, Value, Gas limit, Max fee per gas, Max priority fee per gas, Nonce, Access list) + #[cfg(feature = "evm")] + evm_call( + AccountId, + H160, + H160, + Vec, + U256, + u64, + U256, + Option, + Option, + Vec<(H160, Vec)>, + ), + // (Origin, Source, Init, Value, Gas limit, Max fee per gas, Max priority fee per gas, Nonce, Access list) + #[cfg(feature = "evm")] + evm_create( + AccountId, + H160, + Vec, + U256, + u64, + U256, + Option, + Option, + Vec<(H160, Vec)>, + ), + // (Origin, Source, Init, Salt, Value, Gas limit, Max fee per gas, Max priority fee per gas, Nonce, Access list) + #[cfg(feature = "evm")] + evm_create2( + AccountId, + H160, + Vec, + H256, + U256, + u64, + U256, + Option, + Option, + Vec<(H160, Vec)>, + ), + // litentry + set_user_shielding_key_preflight(AccountId, AccountId, UserShieldingKeyType), // (Root, AccountIncognito, Key) -- root as signer, only for testing + set_user_shielding_key_runtime(AccountId, AccountId, UserShieldingKeyType), // (EnclaveSigner, AccountIncognito, Key) + link_identity_runtime( + AccountId, + AccountId, + Identity, + Option>, + ParentchainBlockNumber, + ), // (EnclaveSigner, Account, identity, metadata, blocknumber) + unlink_identity_runtime(AccountId, AccountId, Identity), // (EnclaveSigner, Account, identity) + verify_identity_preflight( + AccountId, + AccountId, + Identity, + ValidationData, + ParentchainBlockNumber, + ), // (EnclaveSigner, Account, identity, validation, blocknumber) + verify_identity_runtime(AccountId, AccountId, Identity, ParentchainBlockNumber), // (EnclaveSigner, Account, identity, blocknumber) + set_challenge_code_runtime(AccountId, AccountId, Identity, ChallengeCode), // only for testing +} + +impl TrustedCall { + pub fn sender_account(&self) -> &AccountId { + match self { + TrustedCall::balance_set_balance(sender_account, ..) => sender_account, + TrustedCall::balance_transfer(sender_account, ..) => sender_account, + TrustedCall::balance_unshield(sender_account, ..) => sender_account, + TrustedCall::balance_shield(sender_account, ..) => sender_account, + #[cfg(feature = "evm")] + TrustedCall::evm_withdraw(sender_account, ..) => sender_account, + #[cfg(feature = "evm")] + TrustedCall::evm_call(sender_account, ..) => sender_account, + #[cfg(feature = "evm")] + TrustedCall::evm_create(sender_account, ..) => sender_account, + #[cfg(feature = "evm")] + TrustedCall::evm_create2(sender_account, ..) => sender_account, + // litentry + TrustedCall::set_user_shielding_key_preflight(account, _, _) => account, + TrustedCall::set_user_shielding_key_runtime(account, _, _) => account, + TrustedCall::link_identity_runtime(account, _, _, _, _) => account, + TrustedCall::unlink_identity_runtime(account, _, _) => account, + TrustedCall::verify_identity_preflight(account, _, _, _, _) => account, + TrustedCall::verify_identity_runtime(account, _, _, _) => account, + TrustedCall::set_challenge_code_runtime(account, _, _, _) => account, + } + } + + pub fn sign( + &self, + pair: &KeyPair, + nonce: Index, + mrenclave: &[u8; 32], + shard: &ShardIdentifier, + ) -> TrustedCallSigned { + let mut payload = self.encode(); + payload.append(&mut nonce.encode()); + payload.append(&mut mrenclave.encode()); + payload.append(&mut shard.encode()); + + TrustedCallSigned { call: self.clone(), nonce, signature: pair.sign(payload.as_slice()) } + } +} + +#[derive(Encode, Decode, Clone, Debug, PartialEq, Eq)] +pub struct TrustedCallSigned { + pub call: TrustedCall, + pub nonce: Index, + pub signature: Signature, +} + +impl TrustedCallSigned { + pub fn new(call: TrustedCall, nonce: Index, signature: Signature) -> Self { + TrustedCallSigned { call, nonce, signature } + } + + pub fn verify_signature(&self, mrenclave: &[u8; 32], shard: &ShardIdentifier) -> bool { + let mut payload = self.call.encode(); + payload.append(&mut self.nonce.encode()); + payload.append(&mut mrenclave.encode()); + payload.append(&mut shard.encode()); + self.signature.verify(payload.as_slice(), self.call.sender_account()) + } + + pub fn into_trusted_operation(self, direct: bool) -> TrustedOperation { + match direct { + true => TrustedOperation::direct_call(self), + false => TrustedOperation::indirect_call(self), + } + } +} + +// TODO: #91 signed return value +/* +pub struct TrustedReturnValue { + pub value: T, + pub signer: AccountId +} + +impl TrustedReturnValue +*/ + +impl ExecuteCall for TrustedCallSigned +where + NodeMetadataRepository: AccessNodeMetadata, + NodeMetadataRepository::MetadataType: TeerexCallIndexes + IMPCallIndexes, +{ + type Error = StfError; + + fn execute( + self, + shard: &ShardIdentifier, + calls: &mut Vec, + node_metadata_repo: Arc, + ) -> Result<(), Self::Error> { + let sender = self.call.sender_account().clone(); + let call_hash = blake2_256(&self.call.encode()); + ensure!( + self.nonce == System::account_nonce(&sender), + Self::Error::InvalidNonce(self.nonce) + ); + match self.call { + TrustedCall::balance_set_balance(root, who, free_balance, reserved_balance) => { + ensure!(is_root::(&root), Self::Error::MissingPrivileges(root)); + debug!( + "balance_set_balance({}, {}, {})", + account_id_to_string(&who), + free_balance, + reserved_balance + ); + ita_sgx_runtime::BalancesCall::::set_balance { + who: MultiAddress::Id(who), + new_free: free_balance, + new_reserved: reserved_balance, + } + .dispatch_bypass_filter(ita_sgx_runtime::Origin::root()) + .map_err(|e| { + Self::Error::Dispatch(format!("Balance Set Balance error: {:?}", e.error)) + })?; + Ok(()) + }, + TrustedCall::balance_transfer(from, to, value) => { + let origin = ita_sgx_runtime::Origin::signed(from.clone()); + debug!( + "balance_transfer({}, {}, {})", + account_id_to_string(&from), + account_id_to_string(&to), + value + ); + ita_sgx_runtime::BalancesCall::::transfer { + dest: MultiAddress::Id(to), + value, + } + .dispatch_bypass_filter(origin) + .map_err(|e| { + Self::Error::Dispatch(format!("Balance Transfer error: {:?}", e.error)) + })?; + Ok(()) + }, + TrustedCall::balance_unshield(account_incognito, beneficiary, value, shard) => { + debug!( + "balance_unshield({}, {}, {}, {})", + account_id_to_string(&account_incognito), + account_id_to_string(&beneficiary), + value, + shard + ); + unshield_funds(account_incognito, value)?; + calls.push(OpaqueCall::from_tuple(&( + node_metadata_repo.get_from_metadata(|m| m.unshield_funds_call_indexes())??, + beneficiary, + value, + shard, + call_hash, + ))); + Ok(()) + }, + TrustedCall::balance_shield(enclave_account, who, value) => { + ensure_enclave_signer_account(&enclave_account)?; + debug!("balance_shield({}, {})", account_id_to_string(&who), value); + shield_funds(who, value)?; + Ok(()) + }, + #[cfg(feature = "evm")] + TrustedCall::evm_withdraw(from, address, value) => { + debug!("evm_withdraw({}, {}, {})", account_id_to_string(&from), address, value); + ita_sgx_runtime::EvmCall::::withdraw { address, value } + .dispatch_bypass_filter(ita_sgx_runtime::Origin::signed(from)) + .map_err(|e| { + Self::Error::Dispatch(format!("Evm Withdraw error: {:?}", e.error)) + })?; + Ok(()) + }, + #[cfg(feature = "evm")] + TrustedCall::evm_call( + from, + source, + target, + input, + value, + gas_limit, + max_fee_per_gas, + max_priority_fee_per_gas, + nonce, + access_list, + ) => { + debug!( + "evm_call(from: {}, source: {}, target: {})", + account_id_to_string(&from), + source, + target + ); + ita_sgx_runtime::EvmCall::::call { + source, + target, + input, + value, + gas_limit, + max_fee_per_gas, + max_priority_fee_per_gas, + nonce, + access_list, + } + .dispatch_bypass_filter(ita_sgx_runtime::Origin::signed(from)) + .map_err(|e| Self::Error::Dispatch(format!("Evm Call error: {:?}", e.error)))?; + Ok(()) + }, + #[cfg(feature = "evm")] + TrustedCall::evm_create( + from, + source, + init, + value, + gas_limit, + max_fee_per_gas, + max_priority_fee_per_gas, + nonce, + access_list, + ) => { + debug!( + "evm_create(from: {}, source: {}, value: {})", + account_id_to_string(&from), + source, + value + ); + let nonce_evm_account = + System::account_nonce(&HashedAddressMapping::into_account_id(source)); + ita_sgx_runtime::EvmCall::::create { + source, + init, + value, + gas_limit, + max_fee_per_gas, + max_priority_fee_per_gas, + nonce, + access_list, + } + .dispatch_bypass_filter(ita_sgx_runtime::Origin::signed(from)) + .map_err(|e| Self::Error::Dispatch(format!("Evm Create error: {:?}", e.error)))?; + let contract_address = evm_create_address(source, nonce_evm_account); + info!("Trying to create evm contract with address {:?}", contract_address); + Ok(()) + }, + #[cfg(feature = "evm")] + TrustedCall::evm_create2( + from, + source, + init, + salt, + value, + gas_limit, + max_fee_per_gas, + max_priority_fee_per_gas, + nonce, + access_list, + ) => { + debug!( + "evm_create2(from: {}, source: {}, value: {})", + account_id_to_string(&from), + source, + value + ); + let code_hash = create_code_hash(&init); + ita_sgx_runtime::EvmCall::::create2 { + source, + init, + salt, + value, + gas_limit, + max_fee_per_gas, + max_priority_fee_per_gas, + nonce, + access_list, + } + .dispatch_bypass_filter(ita_sgx_runtime::Origin::signed(from)) + .map_err(|e| Self::Error::Dispatch(format!("Evm Create2 error: {:?}", e.error)))?; + let contract_address = evm_create2_address(source, salt, code_hash); + info!("Trying to create evm contract with address {:?}", contract_address); + Ok(()) + }, + // litentry + TrustedCall::set_user_shielding_key_preflight(root, who, key) => { + ensure!(is_root::(&root), Self::Error::MissingPrivileges(root)); + Self::set_user_shielding_key_preflight(shard, who, key) + }, + TrustedCall::set_user_shielding_key_runtime(enclave_account, who, key) => { + ensure_enclave_signer_account(&enclave_account)?; + // TODO: we only checked if the extrinsic dispatch is successful, + // is that enough? (i.e. is the state changed already?) + match Self::set_user_shielding_key_runtime(who.clone(), key) { + Ok(()) => { + calls.push(OpaqueCall::from_tuple(&( + node_metadata_repo + .get_from_metadata(|m| m.user_shielding_key_set_call_indexes())??, + aes_encrypt_default(&key, &who.encode()), + ))); + }, + Err(err) => { + debug!("set_user_shielding_key error: {}", err); + calls.push(OpaqueCall::from_tuple(&( + node_metadata_repo + .get_from_metadata(|m| m.some_error_call_indexes())??, + "set_user_shielding_key".as_bytes(), + format!("{:?}", err).as_bytes(), + ))); + }, + } + Ok(()) + }, + TrustedCall::link_identity_runtime(enclave_account, who, identity, metadata, bn) => { + ensure_enclave_signer_account(&enclave_account)?; + debug!( + "link_identity, who: {}, identity: {:?}, metadata: {:?}", + account_id_to_string(&who), + identity, + metadata + ); + match Self::link_identity_runtime(who.clone(), identity.clone(), metadata, bn) { + Ok(code) => { + debug!("link_identity {} OK", account_id_to_string(&who)); + if let Some(key) = IdentityManagement::user_shielding_keys(&who) { + calls.push(OpaqueCall::from_tuple(&( + node_metadata_repo + .get_from_metadata(|m| m.identity_linked_call_indexes())??, + aes_encrypt_default(&key, &who.encode()), + aes_encrypt_default(&key, &identity.encode()), + ))); + calls.push(OpaqueCall::from_tuple(&( + node_metadata_repo.get_from_metadata(|m| { + m.challenge_code_generated_call_indexes() + })??, + aes_encrypt_default(&key, &who.encode()), + aes_encrypt_default(&key, &identity.encode()), + aes_encrypt_default(&key, &code.encode()), + ))); + } else { + calls.push(OpaqueCall::from_tuple(&( + node_metadata_repo + .get_from_metadata(|m| m.some_error_call_indexes())??, + "get_user_shielding_key".as_bytes(), + "error".as_bytes(), + ))); + } + }, + Err(err) => { + debug!("link_identity {} error: {}", account_id_to_string(&who), err); + calls.push(OpaqueCall::from_tuple(&( + node_metadata_repo + .get_from_metadata(|m| m.some_error_call_indexes())??, + "link_identity".as_bytes(), + format!("{:?}", err).as_bytes(), + ))); + }, + } + Ok(()) + }, + TrustedCall::unlink_identity_runtime(enclave_account, who, identity) => { + ensure_enclave_signer_account(&enclave_account)?; + debug!( + "unlink_identity, who: {}, identity: {:?}", + account_id_to_string(&who), + identity, + ); + match Self::unlink_identity_runtime(who.clone(), identity.clone()) { + Ok(()) => { + debug!("unlink_identity {} OK", account_id_to_string(&who)); + if let Some(key) = IdentityManagement::user_shielding_keys(&who) { + calls.push(OpaqueCall::from_tuple(&( + node_metadata_repo + .get_from_metadata(|m| m.identity_unlinked_call_indexes())??, + aes_encrypt_default(&key, &who.encode()), + aes_encrypt_default(&key, &identity.encode()), + ))); + } else { + calls.push(OpaqueCall::from_tuple(&( + node_metadata_repo + .get_from_metadata(|m| m.some_error_call_indexes())??, + "get_user_shielding_key".as_bytes(), + "error".as_bytes(), + ))); + } + }, + Err(err) => { + debug!("unlink_identity {} error: {}", account_id_to_string(&who), err); + calls.push(OpaqueCall::from_tuple(&( + node_metadata_repo + .get_from_metadata(|m| m.some_error_call_indexes())??, + "unlink_identity".as_bytes(), + format!("{:?}", err).as_bytes(), + ))); + }, + } + Ok(()) + }, + TrustedCall::verify_identity_preflight( + enclave_account, + account, + identity, + validation_data, + bn, + ) => { + ensure_enclave_signer_account(&enclave_account)?; + Self::verify_identity_preflight(shard, account, identity, validation_data, bn) + }, + TrustedCall::verify_identity_runtime(enclave_account, who, identity, bn) => { + ensure_enclave_signer_account(&enclave_account)?; + debug!( + "verify_identity, who: {}, identity: {:?}, bn: {:?}", + account_id_to_string(&who), + identity, + bn + ); + match Self::verify_identity_runtime(who.clone(), identity.clone(), bn) { + Ok(()) => { + debug!("verify_identity {} OK", account_id_to_string(&who)); + if let Some(key) = IdentityManagement::user_shielding_keys(&who) { + calls.push(OpaqueCall::from_tuple(&( + node_metadata_repo + .get_from_metadata(|m| m.identity_verified_call_indexes())??, + aes_encrypt_default(&key, &who.encode()), + aes_encrypt_default(&key, &identity.encode()), + ))); + } else { + calls.push(OpaqueCall::from_tuple(&( + node_metadata_repo + .get_from_metadata(|m| m.some_error_call_indexes())??, + "get_user_shielding_key".as_bytes(), + "error".as_bytes(), + ))); + } + }, + Err(err) => { + debug!("link_identity {} error: {}", account_id_to_string(&who), err); + calls.push(OpaqueCall::from_tuple(&( + node_metadata_repo + .get_from_metadata(|m| m.some_error_call_indexes())??, + "verify_identity".as_bytes(), + format!("{:?}", err).as_bytes(), + ))); + }, + } + Ok(()) + }, + TrustedCall::set_challenge_code_runtime(enclave_account, account, did, code) => { + ensure_enclave_signer_account(&enclave_account)?; + Self::set_challenge_code_runtime(account, did, code) + }, + }?; + System::inc_account_nonce(&sender); + Ok(()) + } + + fn get_storage_hashes_to_update(&self) -> Vec> { + let key_hashes = Vec::new(); + match self.call { + TrustedCall::balance_set_balance(_, _, _, _) => debug!("No storage updates needed..."), + TrustedCall::balance_transfer(_, _, _) => debug!("No storage updates needed..."), + TrustedCall::balance_unshield(_, _, _, _) => debug!("No storage updates needed..."), + TrustedCall::balance_shield(_, _, _) => debug!("No storage updates needed..."), + // litentry + TrustedCall::set_user_shielding_key_preflight(..) => + debug!("No storage updates needed..."), + TrustedCall::set_user_shielding_key_runtime(..) => + debug!("No storage updates needed..."), + TrustedCall::link_identity_runtime(..) => debug!("No storage updates needed..."), + TrustedCall::unlink_identity_runtime(..) => debug!("No storage updates needed..."), + TrustedCall::verify_identity_preflight(..) => debug!("No storage updates needed..."), + TrustedCall::verify_identity_runtime(..) => debug!("No storage updates needed..."), + TrustedCall::set_challenge_code_runtime(..) => debug!("No storage updates needed..."), + #[cfg(feature = "evm")] + _ => debug!("No storage updates needed..."), + }; + key_hashes + } +} + +fn unshield_funds(account: AccountId, amount: u128) -> Result<(), StfError> { + let account_info = System::account(&account); + if account_info.data.free < amount { + return Err(StfError::MissingFunds) + } + + ita_sgx_runtime::BalancesCall::::set_balance { + who: MultiAddress::Id(account), + new_free: account_info.data.free - amount, + new_reserved: account_info.data.reserved, + } + .dispatch_bypass_filter(ita_sgx_runtime::Origin::root()) + .map_err(|e| StfError::Dispatch(format!("Unshield funds error: {:?}", e.error)))?; + Ok(()) +} + +fn shield_funds(account: AccountId, amount: u128) -> Result<(), StfError> { + let account_info = System::account(&account); + ita_sgx_runtime::BalancesCall::::set_balance { + who: MultiAddress::Id(account), + new_free: account_info.data.free + amount, + new_reserved: account_info.data.reserved, + } + .dispatch_bypass_filter(ita_sgx_runtime::Origin::root()) + .map_err(|e| StfError::Dispatch(format!("Shield funds error: {:?}", e.error)))?; + + Ok(()) +} + +fn is_root(account: &AccountId) -> bool +where + Runtime: frame_system::Config + pallet_sudo::Config, + AccountId: PartialEq, +{ + pallet_sudo::Pallet::::key().map_or(false, |k| account == &k) +} + +#[cfg(test)] +mod tests { + use super::*; + use sp_keyring::AccountKeyring; + + #[test] + fn verify_signature_works() { + let nonce = 21; + let mrenclave = [0u8; 32]; + let shard = ShardIdentifier::default(); + + let call = TrustedCall::balance_set_balance( + AccountKeyring::Alice.public().into(), + AccountKeyring::Alice.public().into(), + 42, + 42, + ); + let signed_call = call.sign( + &KeyPair::Sr25519(Box::new(AccountKeyring::Alice.pair())), + nonce, + &mrenclave, + &shard, + ); + + assert!(signed_call.verify_signature(&mrenclave, &shard)); + } +} diff --git a/tee-worker/app-libs/stf/src/trusted_call_litentry.rs b/tee-worker/app-libs/stf/src/trusted_call_litentry.rs new file mode 100644 index 0000000000..fc9a954ee0 --- /dev/null +++ b/tee-worker/app-libs/stf/src/trusted_call_litentry.rs @@ -0,0 +1,224 @@ +// Copyright 2020-2022 Litentry Technologies GmbH. +// This file is part of Litentry. +// +// Litentry is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Litentry is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Litentry. If not, see . + +#[cfg(all(not(feature = "std"), feature = "sgx"))] +extern crate sgx_tstd as std; + +use crate::{ + helpers::{enclave_signer_account, generate_challenge_code}, + AccountId, Encode, IdentityManagement, MetadataOf, Runtime, ShardIdentifier, StfError, + StfResult, TrustedCall, TrustedCallSigned, +}; +use frame_support::dispatch::UnfilteredDispatchable; +use itp_utils::stringify::account_id_to_string; +use lc_stf_task_sender::{ + stf_task_sender::{SendStfRequest, StfRequestSender}, + AssertionBuildRequest, MaxIdentityLength, RequestType, SetUserShieldingKeyRequest, + Web2IdentityVerificationRequest, Web3IdentityVerificationRequest, +}; +use litentry_primitives::{ + Assertion, ChallengeCode, Identity, ParentchainBlockNumber, UserShieldingKeyType, + ValidationData, +}; +use log::*; +use sp_runtime::BoundedVec; +use std::{format, string::ToString, vec}; + +impl TrustedCallSigned { + pub fn set_user_shielding_key_preflight( + shard: &ShardIdentifier, + who: AccountId, + key: UserShieldingKeyType, + ) -> StfResult<()> { + debug!("who.str = {:?}, key = {:?}", account_id_to_string(&who), key.clone()); + let encoded_callback = + TrustedCall::set_user_shielding_key_runtime(enclave_signer_account(), who.clone(), key) + .encode(); + let encoded_shard = shard.encode(); + let request = SetUserShieldingKeyRequest { encoded_shard, who, encoded_callback }.into(); + let sender = StfRequestSender::new(); + sender.send_stf_request(request).map_err(|_| StfError::VerifyIdentityFailed) + } + + pub fn set_user_shielding_key_runtime( + who: AccountId, + key: UserShieldingKeyType, + ) -> StfResult<()> { + ita_sgx_runtime::IdentityManagementCall::::set_user_shielding_key { who, key } + .dispatch_bypass_filter(ita_sgx_runtime::Origin::root()) + .map_err(|e| StfError::Dispatch(format!("{:?}", e.error)))?; + Ok(()) + } + + pub fn link_identity_runtime( + who: AccountId, + identity: Identity, + metadata: Option>, + bn: ParentchainBlockNumber, + ) -> StfResult { + debug!( + "who.str = {:?}, identity = {:?}, metadata = {:?}, bn = {:?}", + account_id_to_string(&who), + identity, + metadata, + bn + ); + + ita_sgx_runtime::IdentityManagementCall::::link_identity { + who: who.clone(), + identity: identity.clone(), + metadata, + linking_request_block: bn, + } + .dispatch_bypass_filter(ita_sgx_runtime::Origin::root()) + .map_err(|e| StfError::Dispatch(format!("{:?}", e.error)))?; + + // generate challenge code + let code = generate_challenge_code(); + ita_sgx_runtime::IdentityManagementCall::::set_challenge_code { + who, + identity, + code, + } + .dispatch_bypass_filter(ita_sgx_runtime::Origin::root()) + .map_err(|e| StfError::Dispatch(format!("{:?}", e.error)))?; + + Ok(code) + } + + pub fn unlink_identity_runtime(who: AccountId, identity: Identity) -> StfResult<()> { + debug!("who.str = {:?}, identity = {:?}", account_id_to_string(&who), identity,); + ita_sgx_runtime::IdentityManagementCall::::unlink_identity { who, identity } + .dispatch_bypass_filter(ita_sgx_runtime::Origin::root()) + .map_err(|e| StfError::Dispatch(format!("{:?}", e.error)))?; + Ok(()) + } + + pub fn verify_identity_preflight( + shard: &ShardIdentifier, + who: AccountId, + identity: Identity, + validation_data: ValidationData, + bn: ParentchainBlockNumber, + ) -> StfResult<()> { + let code = IdentityManagement::challenge_codes(&who, &identity) + .ok_or_else(|| StfError::Dispatch("code not found".to_string()))?; + + debug!("who:{:?}, identity:{:?}, code:{:?}", who, identity, code); + + let encoded_callback = TrustedCall::verify_identity_runtime( + enclave_signer_account(), + who.clone(), + identity.clone(), + bn, + ) + .encode(); + let encoded_shard = shard.encode(); + let request: RequestType = match validation_data { + ValidationData::Web2(web2) => Web2IdentityVerificationRequest { + encoded_shard, + who, + identity, + challenge_code: code, + validation_data: web2, + bn, + encoded_callback, + } + .into(), + ValidationData::Web3(web3) => Web3IdentityVerificationRequest { + encoded_shard, + who, + identity, + challenge_code: code, + validation_data: web3, + bn, + encoded_callback, + } + .into(), + }; + + let sender = StfRequestSender::new(); + sender.send_stf_request(request).map_err(|_| StfError::VerifyIdentityFailed) + } + + pub fn verify_identity_runtime( + who: AccountId, + identity: Identity, + bn: ParentchainBlockNumber, + ) -> StfResult<()> { + debug!( + "who.str = {:?}, identity = {:?}, bn = {:?}", + account_id_to_string(&who), + identity, + bn + ); + ita_sgx_runtime::IdentityManagementCall::::verify_identity { + who: who.clone(), + identity: identity.clone(), + verification_request_block: bn, + } + .dispatch_bypass_filter(ita_sgx_runtime::Origin::root()) + .map_err(|e| StfError::Dispatch(format!("{:?}", e.error)))?; + + // remove challenge code + ita_sgx_runtime::IdentityManagementCall::::remove_challenge_code { who, identity } + .dispatch_bypass_filter(ita_sgx_runtime::Origin::root()) + .map_err(|e| StfError::Dispatch(format!("{:?}", e.error)))?; + + Ok(()) + } + + pub fn build_assertion( + shard: &ShardIdentifier, + who: AccountId, + assertion: Assertion, + ) -> StfResult<()> { + let v_identity_context = + ita_sgx_runtime::pallet_identity_management::Pallet::::get_identity_and_identity_context(&who); + + let mut vec_identity: BoundedVec = vec![].try_into().unwrap(); + + for identity_ctx in &v_identity_context { + if identity_ctx.1.is_verified { + vec_identity + .try_push(identity_ctx.0.clone()) + .map_err(|_| StfError::AssertionBuildFail)?; + } + } + + let encoded_shard = shard.encode(); + let request: RequestType = + AssertionBuildRequest { encoded_shard, who, assertion, vec_identity }.into(); + + let sender = StfRequestSender::new(); + sender.send_stf_request(request).map_err(|_| StfError::AssertionBuildFail) + } + + pub fn set_challenge_code_runtime( + who: AccountId, + identity: Identity, + code: ChallengeCode, + ) -> StfResult<()> { + ita_sgx_runtime::IdentityManagementCall::::set_challenge_code { + who, + identity, + code, + } + .dispatch_bypass_filter(ita_sgx_runtime::Origin::root()) + .map_err(|e| StfError::Dispatch(format!("{:?}", e.error)))?; + Ok(()) + } +} diff --git a/tee-worker/bin/README.md b/tee-worker/bin/README.md new file mode 100644 index 0000000000..9cf10b5eb8 --- /dev/null +++ b/tee-worker/bin/README.md @@ -0,0 +1 @@ +Output directory for the binaries \ No newline at end of file diff --git a/tee-worker/build.Dockerfile b/tee-worker/build.Dockerfile new file mode 100644 index 0000000000..5aa23778ce --- /dev/null +++ b/tee-worker/build.Dockerfile @@ -0,0 +1,141 @@ +# Copyright 2021 Integritee AG +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# This is a multi-stage docker file, where the first stage is used +# for building and the second deploys the built application. + +### Builder Stage +################################################## +FROM integritee/integritee-dev:0.1.9 AS builder +LABEL maintainer="zoltan@integritee.network" + +# set environment variables +ENV SGX_SDK /opt/sgxsdk +ENV PATH "$PATH:${SGX_SDK}/bin:${SGX_SDK}/bin/x64:/root/.cargo/bin" +ENV PKG_CONFIG_PATH "${PKG_CONFIG_PATH}:${SGX_SDK}/pkgconfig" +ENV LD_LIBRARY_PATH "${LD_LIBRARY_PATH}:${SGX_SDK}/sdk_libs" +ENV CARGO_NET_GIT_FETCH_WITH_CLI true +ENV SGX_MODE SW + +ENV HOME=/root/work + +ARG WORKER_MODE_ARG +ENV WORKER_MODE=$WORKER_MODE_ARG + +ARG ADDITIONAL_FEATURES_ARG +ENV ADDITIONAL_FEATURES=$ADDITIONAL_FEATURES_ARG + +WORKDIR $HOME/worker +COPY . . + +RUN make + +RUN cargo test --release + + +### Cached Builder Stage (WIP) +################################################## +# A builder stage that uses sccache to speed up local builds with docker +# Installation and setup of sccache should be moved to the integritee-dev image, so we don't +# always need to compile and install sccache on CI (where we have no caching so far). +FROM integritee/integritee-dev:0.1.9 AS cached-builder +LABEL maintainer="zoltan@integritee.network" + +# set environment variables +ENV SGX_SDK /opt/sgxsdk +ENV PATH "$PATH:${SGX_SDK}/bin:${SGX_SDK}/bin/x64:/root/.cargo/bin" +ENV PKG_CONFIG_PATH "${PKG_CONFIG_PATH}:${SGX_SDK}/pkgconfig" +ENV LD_LIBRARY_PATH "${LD_LIBRARY_PATH}:${SGX_SDK}/sdk_libs" +ENV CARGO_NET_GIT_FETCH_WITH_CLI true +ENV SGX_MODE SW + +ENV HOME=/root/work + +RUN rustup default stable && cargo install sccache --root /usr/local/cargo +ENV PATH "$PATH:/usr/local/cargo/bin" +ENV SCCACHE_CACHE_SIZE="3G" +ENV SCCACHE_DIR=$HOME/.cache/sccache +ENV RUSTC_WRAPPER="/usr/local/cargo/bin/sccache" + +ARG WORKER_MODE_ARG +ENV WORKER_MODE=$WORKER_MODE_ARG + +WORKDIR $HOME/worker +COPY . . + +RUN --mount=type=cache,id=cargo,target=/root/work/.cache/sccache make && sccache --show-stats + +RUN --mount=type=cache,id=cargo,target=/root/work/.cache/sccache cargo test --release && sccache --show-stats + + +### Base Runner Stage +################################################## +FROM ubuntu:20.04 AS runner + +RUN apt update && apt install -y libssl-dev iproute2 + +COPY --from=powerman/dockerize /usr/local/bin/dockerize /usr/local/bin/dockerize + + +### Deployed CLI client +################################################## +FROM runner AS deployed-client +LABEL maintainer="zoltan@integritee.network" + +ARG SCRIPT_DIR=/usr/local/worker-cli +ARG LOG_DIR=/usr/local/log + +ENV SCRIPT_DIR ${SCRIPT_DIR} +ENV LOG_DIR ${LOG_DIR} + +COPY --from=builder /root/work/worker/bin/integritee-cli /usr/local/bin +COPY ./cli/*.sh /usr/local/worker-cli/ + +RUN chmod +x /usr/local/bin/integritee-cli ${SCRIPT_DIR}/*.sh +RUN mkdir ${LOG_DIR} + +RUN ldd /usr/local/bin/integritee-cli && \ + /usr/local/bin/integritee-cli --version + +## ts-tests +RUN apt-get install -y curl +RUN curl -fsSL https://deb.nodesource.com/setup_18.x | bash +RUN apt-get install -y nodejs +RUN npm install -g yarn + +ENTRYPOINT ["/usr/local/bin/integritee-cli"] + + +### Deployed worker service +################################################## +FROM runner AS deployed-worker +LABEL maintainer="zoltan@integritee.network" + +ENV SGX_SDK /opt/sgxsdk +ENV LD_LIBRARY_PATH "${LD_LIBRARY_PATH}:${SGX_SDK}/lib64" + +WORKDIR /usr/local/bin + +COPY --from=builder /opt/sgxsdk/lib64 /opt/sgxsdk/lib64 +COPY --from=builder /root/work/worker/bin/* ./ + +RUN touch spid.txt key.txt +RUN chmod +x /usr/local/bin/integritee-service +RUN ls -al /usr/local/bin + +# checks +RUN ldd /usr/local/bin/integritee-service && \ + /usr/local/bin/integritee-service --version + +ENTRYPOINT ["/usr/local/bin/integritee-service"] diff --git a/tee-worker/cli/Cargo.toml b/tee-worker/cli/Cargo.toml new file mode 100644 index 0000000000..8e183ac44c --- /dev/null +++ b/tee-worker/cli/Cargo.toml @@ -0,0 +1,65 @@ +[package] +authors = ["Integritee AG "] +edition = "2021" +name = "integritee-cli" +version = "0.9.0" + +[dependencies] +base58 = "0.2" +blake2-rfc = { version = "0.2.18" } +chrono = "*" +clap = { version = "3.1.6", features = ["derive"] } +codec = { version = "3.0.0", package = "parity-scale-codec", features = ["derive"] } +env_logger = "0.9" +hdrhistogram = "7.5.0" +hex = "0.4.2" +log = "0.4" +primitive-types = { version = "0.11.1", features = ["codec"] } +rand = "0.8.5" +rayon = "1.5.1" +serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0" +sgx_crypto_helper = { branch = "master", git = "https://github.com/apache/teaclave-sgx-sdk.git" } +ws = { version = "0.9.1", features = ["ssl"] } + +# scs / integritee +my-node-runtime = { package = "rococo-parachain-runtime", git = "https://github.com/litentry/litentry-parachain.git", branch = "tee-dev" } +pallet-evm = { optional = true, git = "https://github.com/integritee-network/frontier.git", branch = "polkadot-v0.9.29" } +substrate-api-client = { features = ["ws-client"], git = "https://github.com/scs/substrate-api-client.git", branch = "polkadot-v0.9.29" } +substrate-client-keystore = { git = "https://github.com/scs/substrate-api-client.git", branch = "polkadot-v0.9.29" } +teerex-primitives = { git = "https://github.com/integritee-network/pallets.git", branch = "master" } + +# substrate dependencies +frame-system = { git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.29" } +pallet-balances = { git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.29" } +sc-keystore = { git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.29" } +sp-application-crypto = { git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.29" } +sp-core = { git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.29" } +sp-keyring = { git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.29" } +sp-runtime = { git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.29" } + +# local dependencies +ita-sgx-runtime = { path = "../app-libs/sgx-runtime" } +ita-stf = { path = "../app-libs/stf" } +itc-rpc-client = { path = "../core/rpc-client" } +itp-node-api = { path = "../core-primitives/node-api" } +itp-rpc = { path = "../core-primitives/rpc" } +itp-sgx-crypto = { path = "../core-primitives/sgx/crypto" } +itp-time-utils = { path = "../core-primitives/time-utils" } +itp-types = { path = "../core-primitives/types" } +itp-utils = { path = "../core-primitives/utils" } + +# litentry +litentry-primitives = { path = "../litentry/primitives" } + +[features] +default = [] +evm = [ + "ita-stf/evm_std", + "pallet-evm", +] +mockserver = [] +offchain-worker = [] +production = [] +sidechain = [] +teeracle = [] diff --git a/tee-worker/cli/README.md b/tee-worker/cli/README.md new file mode 100644 index 0000000000..f2bf98121c --- /dev/null +++ b/tee-worker/cli/README.md @@ -0,0 +1,27 @@ +# Integritee CLI client +Interact with the Integritee chain and workers from the command line + +Includes +* keystore (incompatible with polkadot js app json) +* basic balance transfer +* Integritee-specific calls + +## examples +``` +> ./integritee-cli transfer //Bob //Alice 12345 +> ./integritee-cli -u ws://127.0.0.1 list-workers +number of workers registered: 1 +Enclave 1 + AccountId: 5HN8RGEiJuc9iNA3vfiYj7Lk6ULWzBZXvSDheohBu3usSUqn + MRENCLAVE: 4GMb72Acyg8hnnnGEJ89jZK5zxNC4LvSe2ME96wLRV6J + RA timestamp: 2022-03-16 10:43:12.001 UTC + URL: wss://127.0.0.1:2345 +> ./integritee-cli -P 2345 trusted --direct --mrenclave 4GMb72Acyg8hnnn +GE4LvSe2ME96wLRV6J unshield-funds //Bob //Alice 12345 +from ss58 is 5FHneW46xGXgs5mUiveU4sbTyGBzmstUspZC92UhjJM694ty +to ss58 is 5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY +send trusted call unshield_funds from 5FHneW46xGXgs5mUiveU4sbTyGBzmstUspZC92UhjJM694ty to 5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY: 12345 +Trusted call 0x69ddfd1698bd2d629180c2dca34ce7add087526c51f43cf68245241b3f13154e is Submitted +Trusted call 0x69ddfd1698bd2d629180c2dca34ce7add087526c51f43cf68245241b3f13154e is Invalid + +``` diff --git a/tee-worker/cli/benchmark.sh b/tee-worker/cli/benchmark.sh new file mode 100644 index 0000000000..d0ad5a6465 --- /dev/null +++ b/tee-worker/cli/benchmark.sh @@ -0,0 +1,61 @@ +#!/bin/bash + +while getopts ":m:p:A:u:V:C:" opt; do + case $opt in + m) + READMRENCLAVE=$OPTARG + ;; + p) + NPORT=$OPTARG + ;; + A) + WORKER1PORT=$OPTARG + ;; + u) + NODEURL=$OPTARG + ;; + V) + WORKER1URL=$OPTARG + ;; + C) + CLIENT_BIN=$OPTARG + ;; + *) + ;; + esac +done + +# using default port if none given as arguments +NPORT=${NPORT:-9944} +NODEURL=${NODEURL:-"ws://127.0.0.1"} + +WORKER1PORT=${WORKER1PORT:-2000} +WORKER1URL=${WORKER1URL:-"wss://127.0.0.1"} + +CLIENT_BIN=${CLIENT_BIN:-"./../bin/integritee-cli"} + +echo "Using client binary ${CLIENT_BIN}" +echo "Using node uri ${NODEURL}:${NPORT}" +echo "Using trusted-worker uri ${WORKER1URL}:${WORKER1PORT}" + +CLIENTWORKER1="${CLIENT_BIN} -p ${NPORT} -P ${WORKER1PORT} -u ${NODEURL} -U ${WORKER1URL}" + +if [ "$READMRENCLAVE" = "file" ] +then + read -r MRENCLAVE <<< "$(cat ~/mrenclave.b58)" + echo "Reading MRENCLAVE from file: ${MRENCLAVE}" +else + # this will always take the first MRENCLAVE found in the registry !! + read -r MRENCLAVE <<< "$($CLIENTWORKER1 list-workers | awk '/ MRENCLAVE: / { print $2; exit }')" + echo "Reading MRENCLAVE from worker list: ${MRENCLAVE}" +fi +[[ -z $MRENCLAVE ]] && { echo "MRENCLAVE is empty. cannot continue" ; exit 1; } + +# needed when many clients are started +ulimit -S -n 4096 + +echo "Starting benchmark" +${CLIENTWORKER1} trusted --direct --mrenclave "${MRENCLAVE}" benchmark 20 100 -w +echo "" + +exit 0 diff --git a/tee-worker/cli/demo_direct_call.sh b/tee-worker/cli/demo_direct_call.sh new file mode 100755 index 0000000000..6f6f1f921e --- /dev/null +++ b/tee-worker/cli/demo_direct_call.sh @@ -0,0 +1,135 @@ +#!/bin/bash + +# Executes a direct call on a worker and checks the balance afterwards. +# +# setup: +# run all on localhost: +# integritee-node purge-chain --dev +# integritee-node --tmp --dev -lruntime=debug +# rm light_client_db.bin +# export RUST_LOG=integritee_service=info,ita_stf=debug +# integritee-service init_shard +# integritee-service shielding-key +# integritee-service signing-key +# integritee-service run +# +# then run this script + +# usage: +# demo_direct_call.sh -p -P -t -m file +# +# TEST_BALANCE_RUN is either "first" or "second" +# if -m file is set, the mrenclave will be read from file + +while getopts ":m:p:P:t:u:V:C:" opt; do + case $opt in + t) + TEST=$OPTARG + ;; + m) + READMRENCLAVE=$OPTARG + ;; + p) + NPORT=$OPTARG + ;; + P) + WORKER1PORT=$OPTARG + ;; + u) + NODEURL=$OPTARG + ;; + V) + WORKER1URL=$OPTARG + ;; + C) + CLIENT_BIN=$OPTARG + ;; + esac +done + +# Using default port if none given as arguments. +NPORT=${NPORT:-9944} +NODEURL=${NODEURL:-"ws://127.0.0.1"} + +WORKER1PORT=${WORKER1PORT:-2000} +WORKER1URL=${WORKER1URL:-"wss://127.0.0.1"} + +CLIENT_BIN=${CLIENT_BIN:-"./../bin/integritee-cli"} + +echo "Using client binary ${CLIENT_BIN}" +echo "Using node uri ${NODEURL}:${NPORT}" +echo "Using trusted-worker uri ${WORKER1URL}:${WORKER1PORT}" +echo "" + +AMOUNTSHIELD=50000000000 +AMOUNTTRANSFER=40000000000 + +CLIENT="${CLIENT_BIN} -p ${NPORT} -P ${WORKER1PORT} -u ${NODEURL} -U ${WORKER1URL}" +read -r MRENCLAVE <<< "$($CLIENT list-workers | awk '/ MRENCLAVE: / { print $2; exit }')" + +echo "" +echo "* Create a new incognito account for Alice" +ICGACCOUNTALICE=//AliceIncognito +echo " Alice's incognito account = ${ICGACCOUNTALICE}" +echo "" + +echo "* Create a new incognito account for Bob" +ICGACCOUNTBOB=//BobIncognito +echo " Bob's incognito account = ${ICGACCOUNTBOB}" +echo "" + +echo "* Issue ${AMOUNTSHIELD} tokens to Alice's incognito account" +${CLIENT} trusted --mrenclave ${MRENCLAVE} --direct set-balance ${ICGACCOUNTALICE} ${AMOUNTSHIELD} +echo "" + +echo "Get balance of Alice's incognito account" +${CLIENT} trusted --mrenclave ${MRENCLAVE} balance ${ICGACCOUNTALICE} +echo "" + +# Send funds from Alice to Bob's account. +echo "* Send ${AMOUNTTRANSFER} funds from Alice's incognito account to Bob's incognito account" +$CLIENT trusted --mrenclave ${MRENCLAVE} --direct transfer ${ICGACCOUNTALICE} ${ICGACCOUNTBOB} ${AMOUNTTRANSFER} +echo "" + +# Prevent getter being executed too early and returning an outdated result, before the transfer was made. +echo "* Waiting 2 seconds" +sleep 2 +echo "" + +echo "* Get balance of Alice's incognito account" +RESULT=$(${CLIENT} trusted --mrenclave ${MRENCLAVE} balance ${ICGACCOUNTALICE} | xargs) +echo $RESULT +echo "" + +echo "* Bob's incognito account balance" +RESULT=$(${CLIENT} trusted --mrenclave ${MRENCLAVE} balance ${ICGACCOUNTBOB} | xargs) +echo $RESULT +echo "" + + +# The following tests are for automated CI. +# They only work if you're running from fresh genesis. +case $TEST in + first) + if [ "40000000000" = "$RESULT" ]; then + echo "test passed (1st time)" + echo "" + exit 0 + else + echo "test ran through but balance is wrong. have you run the script from fresh genesis?" + exit 1 + fi + ;; + second) + if [ "80000000000" = "$RESULT" ]; then + echo "test passed (2nd time)" + echo "" + exit 0 + else + echo "test ran through but balance is wrong. is this really the second time you run this since genesis?" + exit 1 + fi + ;; +esac + +exit 0 diff --git a/tee-worker/cli/demo_direct_call_2_workers.sh b/tee-worker/cli/demo_direct_call_2_workers.sh new file mode 100755 index 0000000000..c92615e31d --- /dev/null +++ b/tee-worker/cli/demo_direct_call_2_workers.sh @@ -0,0 +1,53 @@ +#!/bin/bash +set -euo pipefail + +# Runs the `demo_direct_call.sh` twice once with worker1 and worker2. +# This verifies that the two workers are successfully sharing state updates +# by broadcasting sidechain blocks. +# +# It does the same as `scripts/m8.sh`, but is mainly used in the docker tests. + +while getopts ":p:A:B:u:W:V:C:" opt; do + case $opt in + p) + NPORT=$OPTARG + ;; + A) + WORKER1PORT=$OPTARG + ;; + B) + WORKER2PORT=$OPTARG + ;; + u) + NODEURL=$OPTARG + ;; + V) + WORKER1URL=$OPTARG + ;; + W) + WORKER2URL=$OPTARG + ;; + C) + CLIENT_BIN=$OPTARG + ;; + esac +done + +# Using default port if none given as arguments. +NPORT=${NPORT:-9944} +NODEURL=${NODEURL:-"ws://127.0.0.1"} + +WORKER1PORT=${WORKER1PORT:-2000} +WORKER1URL=${WORKER1URL:-"wss://127.0.0.1"} + +WORKER2PORT=${WORKER2PORT:-3000} +WORKER2URL=${WORKER2URL:-"wss://127.0.0.1"} + +CLIENT_BIN=${CLIENT_BIN:-"./../bin/integritee-cli"} + +SCRIPT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) + +"${SCRIPT_DIR}"/demo_direct_call.sh -p "${NPORT}" -u "${NODEURL}" -V "${WORKER1URL}" -P "${WORKER1PORT}" -C "${CLIENT_BIN}" -t first +"${SCRIPT_DIR}"/demo_direct_call.sh -p "${NPORT}" -u "${NODEURL}" -V "${WORKER2URL}" -P "${WORKER2PORT}" -C "${CLIENT_BIN}" -t second + +exit 0 \ No newline at end of file diff --git a/tee-worker/cli/demo_https_test.sh b/tee-worker/cli/demo_https_test.sh new file mode 100755 index 0000000000..4970982edf --- /dev/null +++ b/tee-worker/cli/demo_https_test.sh @@ -0,0 +1,83 @@ +#!/bin/bash + +# setup: +# run all on localhost: +# integritee-node purge-chain --dev +# integritee-node --dev -lruntime=debug +# rm light_client_db.bin +# export RUST_LOG=integritee_service=info,ita_stf=debug +# integritee-service init_shard +# integritee-service shielding-key +# integritee-service signing-key +# integritee-service run +# +# then run this script + +# usage: +# export RUST_LOG_LOG=integritee-cli=info,ita_stf=info +# demo_shielding_unshielding.sh -p -P -t -m file +# +# TEST_BALANCE_RUN is either "first" or "second" +# if -m file is set, the mrenclave will be read from file + +while getopts ":p:A:B:u:W:V:C:" opt; do + case $opt in + p) + NPORT=$OPTARG + ;; + A) + WORKER1PORT=$OPTARG + ;; + B) + WORKER2PORT=$OPTARG + ;; + u) + NODEURL=$OPTARG + ;; + V) + WORKER1URL=$OPTARG + ;; + W) + WORKER2URL=$OPTARG + ;; + C) + CLIENT_BIN=$OPTARG + ;; + esac +done + +# using default port if none given as arguments +NPORT=${NPORT:-9944} +NODEURL=${NODEURL:-"ws://127.0.0.1"} + +WORKER1PORT=${WORKER1PORT:-2000} +WORKER1URL=${WORKER1URL:-"wss://127.0.0.1"} + +CLIENT=${CLIENT_BIN:-"../bin/integritee-cli"} + +echo "Using node-port ${NPORT}" +echo "Using trusted-worker-port ${RPORT}" +echo "" + +CLIENT="$CLIENT -p $NPORT -P $WORKER1PORT -u $NODEURL -U $WORKER1URL" +echo "CLIENT is $CLIENT" + +echo "* Query on-chain enclave registry:" +${CLIENT} list-workers +echo "" + +if [ "$READMRENCLAVE" = "file" ] +then + read MRENCLAVE <<< $(cat ~/mrenclave.b58) + echo "Reading MRENCLAVE from file: ${MRENCLAVE}" +else + # this will always take the first MRENCLAVE found in the registry !! + read MRENCLAVE <<< $($CLIENT list-workers | awk '/ MRENCLAVE: / { print $2; exit }') + echo "Reading MRENCLAVE from worker list: ${MRENCLAVE}" +fi +[[ -z $MRENCLAVE ]] && { echo "MRENCLAVE is empty. cannot continue" ; exit 1; } + + +echo "query-credit: https test" +${CLIENT} trusted --mrenclave ${MRENCLAVE} query-credit "//Alice" +echo "" diff --git a/tee-worker/cli/demo_indirect_invocation.sh b/tee-worker/cli/demo_indirect_invocation.sh new file mode 100755 index 0000000000..68bdf3522b --- /dev/null +++ b/tee-worker/cli/demo_indirect_invocation.sh @@ -0,0 +1,51 @@ +#!/bin/bash +set -euo pipefail + +# Runs the direct call demo twice, with worker 1 and worker 2. +# +# It does the same as `./scripts/m6.sh`, but is mainly used in the docker tests. + +while getopts ":p:A:B:u:W:V:C:" opt; do + case $opt in + p) + NPORT=$OPTARG + ;; + A) + WORKER1PORT=$OPTARG + ;; + B) + WORKER2PORT=$OPTARG + ;; + u) + NODEURL=$OPTARG + ;; + V) + WORKER1URL=$OPTARG + ;; + W) + WORKER2URL=$OPTARG + ;; + C) + CLIENT_BIN=$OPTARG + ;; + esac +done + +# Using default port if none given as arguments. +NPORT=${NPORT:-9944} +NODEURL=${NODEURL:-"ws://127.0.0.1"} + +WORKER1PORT=${WORKER1PORT:-2000} +WORKER1URL=${WORKER1URL:-"wss://127.0.0.1"} + +WORKER2PORT=${WORKER2PORT:-3000} +WORKER2URL=${WORKER2URL:-"wss://127.0.0.1"} + +CLIENT_BIN=${CLIENT_BIN:-"./../bin/integritee-cli"} + +SCRIPT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) + +"${SCRIPT_DIR}"/demo_shielding_unshielding.sh -p "${NPORT}" -u "${NODEURL}" -V "${WORKER1URL}" -P "${WORKER1PORT}" -C "${CLIENT_BIN}" -t first +"${SCRIPT_DIR}"/demo_shielding_unshielding.sh -p "${NPORT}" -u "${NODEURL}" -V "${WORKER2URL}" -P "${WORKER2PORT}" -C "${CLIENT_BIN}" -t second + +exit 0 \ No newline at end of file diff --git a/tee-worker/cli/demo_shielding_unshielding.sh b/tee-worker/cli/demo_shielding_unshielding.sh new file mode 100755 index 0000000000..abacfad588 --- /dev/null +++ b/tee-worker/cli/demo_shielding_unshielding.sh @@ -0,0 +1,175 @@ +#!/bin/bash + +# Demonstrates how to shield tokens from the parentchain into the sidechain. +# +# setup: +# run all on localhost: +# integritee-node purge-chain --dev +# integritee-node --dev -lruntime=debug +# rm light_client_db.bin +# export RUST_LOG=integritee_service=info,ita_stf=debug +# integritee-service init_shard +# integritee-service shielding-key +# integritee-service signing-key +# integritee-service run +# +# then run this script + +# usage: +# demo_shielding_unshielding.sh -p -P -t -m file +# +# TEST_BALANCE_RUN is either "first" or "second" +# if -m file is set, the mrenclave will be read from file + +while getopts ":m:p:P:t:u:V:C:" opt; do + case $opt in + t) + TEST=$OPTARG + ;; + m) + READMRENCLAVE=$OPTARG + ;; + p) + NPORT=$OPTARG + ;; + P) + WORKER1PORT=$OPTARG + ;; + u) + NODEURL=$OPTARG + ;; + V) + WORKER1URL=$OPTARG + ;; + C) + CLIENT_BIN=$OPTARG + ;; + esac +done + +# Using default port if none given as arguments. +NPORT=${NPORT:-9944} +NODEURL=${NODEURL:-"ws://127.0.0.1"} + +WORKER1PORT=${WORKER1PORT:-2000} +WORKER1URL=${WORKER1URL:-"wss://127.0.0.1"} + +CLIENT_BIN=${CLIENT_BIN:-"./../bin/integritee-cli"} + +echo "Using client binary ${CLIENT_BIN}" +echo "Using node uri ${NODEURL}:${NPORT}" +echo "Using trusted-worker uri ${WORKER1URL}:${WORKER1PORT}" +echo "" + +AMOUNTSHIELD=50000000000 +AMOUNTTRANSFER=25000000000 +AMOUNTUNSHIELD=15000000000 + +CLIENT="${CLIENT_BIN} -p ${NPORT} -P ${WORKER1PORT} -u ${NODEURL} -U ${WORKER1URL}" + +echo "* Query on-chain enclave registry:" +${CLIENT} list-workers +echo "" + +if [ "$READMRENCLAVE" = "file" ] +then + read MRENCLAVE <<< $(cat ~/mrenclave.b58) + echo "Reading MRENCLAVE from file: ${MRENCLAVE}" +else + # this will always take the first MRENCLAVE found in the registry !! + read MRENCLAVE <<< $($CLIENT list-workers | awk '/ MRENCLAVE: / { print $2; exit }') + echo "Reading MRENCLAVE from worker list: ${MRENCLAVE}" +fi +[[ -z $MRENCLAVE ]] && { echo "MRENCLAVE is empty. cannot continue" ; exit 1; } + +echo "* Get balance of Alice's on-chain account" +${CLIENT} balance "//Alice" +echo "" + +echo "* Get balance of Bob's on-chain account" +${CLIENT} balance "//Bob" +echo "" + +echo "* Create a new incognito account for Alice" +#ICGACCOUNTALICE=$(${CLIENT} trusted new-account --mrenclave ${MRENCLAVE}) +ICGACCOUNTALICE=//AliceIncognito +echo " Alice's incognito account = ${ICGACCOUNTALICE}" +echo "" + +echo "* Create a new incognito account for Bob" +ICGACCOUNTBOB=$(${CLIENT} trusted --mrenclave ${MRENCLAVE} new-account ) +echo " Bob's incognito account = ${ICGACCOUNTBOB}" +echo "" + +# Sometimes we get a nonce clash here, so let's wait a little bit to prevent that. +sleep 10 + +echo "* Shield ${AMOUNTSHIELD} tokens to Alice's incognito account" +${CLIENT} shield-funds //Alice ${ICGACCOUNTALICE} ${AMOUNTSHIELD} ${MRENCLAVE} +echo "" + +echo "* Waiting 30 seconds" +sleep 30 +echo "" + +echo "Get balance of Alice's incognito account" +${CLIENT} trusted --mrenclave ${MRENCLAVE} balance ${ICGACCOUNTALICE} +echo "" + +echo "* Get balance of Alice's on-chain account" +${CLIENT} balance "//Alice" +echo "" + +echo "* Send ${AMOUNTTRANSFER} funds from Alice's incognito account to Bob's incognito account" +$CLIENT trusted --mrenclave ${MRENCLAVE} transfer ${ICGACCOUNTALICE} ${ICGACCOUNTBOB} ${AMOUNTTRANSFER} +echo "" + +echo "* Get balance of Alice's incognito account" +${CLIENT} trusted --mrenclave ${MRENCLAVE} balance ${ICGACCOUNTALICE} +echo "" + +echo "* Bob's incognito account balance" +${CLIENT} trusted --mrenclave ${MRENCLAVE} balance ${ICGACCOUNTBOB} +echo "" + +echo "* Un-shield ${AMOUNTUNSHIELD} tokens from Alice's incognito account" +${CLIENT} trusted --mrenclave ${MRENCLAVE} --xt-signer //Alice unshield-funds ${ICGACCOUNTALICE} //Alice ${AMOUNTUNSHIELD} +echo "" + +echo "* Waiting 30 seconds" +sleep 30 +echo "" + +echo "Get balance of Alice's incognito account" +RESULT=$(${CLIENT} trusted --mrenclave ${MRENCLAVE} balance ${ICGACCOUNTALICE} | xargs) +echo $RESULT + +echo "* Get balance of Alice's on-chain account" +${CLIENT} balance "//Alice" +echo "" + + +# The following tests are for automated CI. +# They only work if you're running from fresh genesis. +case $TEST in + first) + if [ "10000000000" = "$RESULT" ]; then + echo "test passed (1st time)" + exit 0 + else + echo "test ran through but balance is wrong. have you run the script from fresh genesis?" + exit 1 + fi + ;; + second) + if [ "20000000000" = "$RESULT" ]; then + echo "test passed (2nd time)" + exit 0 + else + echo "test ran through but balance is wrong. is this really the second time you run this since genesis?" + exit 1 + fi + ;; +esac + +exit 0 diff --git a/tee-worker/cli/demo_sidechain.sh b/tee-worker/cli/demo_sidechain.sh new file mode 100755 index 0000000000..4c80288858 --- /dev/null +++ b/tee-worker/cli/demo_sidechain.sh @@ -0,0 +1,167 @@ +#!/bin/bash + +# Sidechain Demo: +# +# Demonstrates that transfers happening on worker1 are communicated via sidechain blocks to worker2. +# It does essentially the same as `m8.sh`, but in one script and more streamlined. +# +# setup: +# run all on localhost: +# integritee-node purge-chain --dev +# integritee-node --tmp --dev -lruntime=debug +# rm light_client_db.bin +# export RUST_LOG=integritee_service=info,ita_stf=debug +# integritee-service init_shard +# integritee-service shielding-key +# integritee-service signing-key +# integritee-service run +# +# Then run this script. +# +# usage: +# export RUST_LOG_LOG=integritee-cli=info,ita_stf=info +# demo_sidechain.sh -p -A -B -m file +# +# TEST_BALANCE_RUN is either "first" or "second" +# if -m file is set, the mrenclave will be read from file. + +while getopts ":m:p:A:B:t:u:W:V:C:" opt; do + case $opt in + t) + TEST=$OPTARG + ;; + m) + READMRENCLAVE=$OPTARG + ;; + p) + NPORT=$OPTARG + ;; + A) + WORKER1PORT=$OPTARG + ;; + B) + WORKER2PORT=$OPTARG + ;; + u) + NODEURL=$OPTARG + ;; + V) + WORKER1URL=$OPTARG + ;; + W) + WORKER2URL=$OPTARG + ;; + C) + CLIENT_BIN=$OPTARG + ;; + esac +done + +# Using default port if none given as arguments. +NPORT=${NPORT:-9944} +NODEURL=${NODEURL:-"ws://127.0.0.1"} + +WORKER1PORT=${WORKER1PORT:-2000} +WORKER1URL=${WORKER1URL:-"wss://127.0.0.1"} + +WORKER2PORT=${WORKER2PORT:-3000} +WORKER2URL=${WORKER2URL:-"wss://127.0.0.1"} + +CLIENT_BIN=${CLIENT_BIN:-"./../bin/integritee-cli"} + +echo "Using client binary ${CLIENT_BIN}" +echo "Using node uri ${NODEURL}:${NPORT}" +echo "Using trusted-worker uri ${WORKER1URL}:${WORKER1PORT}" +echo "Using trusted-worker-2 uri ${WORKER2URL}:${WORKER2PORT}" + +INITIALFUNDS=50000000000 +AMOUNTTRANSFER=20000000000 + +CLIENTWORKER1="${CLIENT_BIN} -p ${NPORT} -P ${WORKER1PORT} -u ${NODEURL} -U ${WORKER1URL}" +CLIENTWORKER2="${CLIENT_BIN} -p ${NPORT} -P ${WORKER2PORT} -u ${NODEURL} -U ${WORKER2URL}" + +if [ "$READMRENCLAVE" = "file" ] +then + read MRENCLAVE <<< $(cat ~/mrenclave.b58) + echo "Reading MRENCLAVE from file: ${MRENCLAVE}" +else + # This will always take the first MRENCLAVE found in the registry !! + read MRENCLAVE <<< $($CLIENTWORKER1 list-workers | awk '/ MRENCLAVE: / { print $2; exit }') + echo "Reading MRENCLAVE from worker list: ${MRENCLAVE}" +fi +[[ -z $MRENCLAVE ]] && { echo "MRENCLAVE is empty. cannot continue" ; exit 1; } + +echo "" +echo "* Create a new incognito account for Alice" +ICGACCOUNTALICE=//AliceIncognito +echo " Alice's incognito account = ${ICGACCOUNTALICE}" +echo "" + +echo "* Create a new incognito account for Bob" +ICGACCOUNTBOB=//BobIncognito +echo " Bob's incognito account = ${ICGACCOUNTBOB}" +echo "" + +echo "* Issue ${INITIALFUNDS} tokens to Alice's incognito account (on worker 1)" +${CLIENTWORKER1} trusted --mrenclave ${MRENCLAVE} --direct set-balance ${ICGACCOUNTALICE} ${INITIALFUNDS} +echo "" + +# see bob's initial balance to 0 +${CLIENTWORKER1} trusted --mrenclave ${MRENCLAVE} --direct set-balance ${ICGACCOUNTBOB} 0 + +echo "Get balance of Alice's incognito account (on worker 1)" +${CLIENTWORKER1} trusted --mrenclave ${MRENCLAVE} balance ${ICGACCOUNTALICE} +echo "" + +# Send funds from Alice to Bobs account, on worker 1. +echo "* First transfer: Send ${AMOUNTTRANSFER} funds from Alice's incognito account to Bob's incognito account (on worker 1)" +$CLIENTWORKER1 trusted --mrenclave ${MRENCLAVE} --direct transfer ${ICGACCOUNTALICE} ${ICGACCOUNTBOB} ${AMOUNTTRANSFER} +echo "" + +# Prevent nonce clash when sending direct trusted calls to different workers. +echo "* Waiting 2 seconds" +sleep 2 +echo "" + +# Send funds from Alice to Bobs account, on worker 2. +echo "* Second transfer: Send ${AMOUNTTRANSFER} funds from Alice's incognito account to Bob's incognito account (on worker 2)" +$CLIENTWORKER2 trusted --mrenclave ${MRENCLAVE} --direct transfer ${ICGACCOUNTALICE} ${ICGACCOUNTBOB} ${AMOUNTTRANSFER} +echo "" + +# Prevent getter being executed too early and returning an outdated result, before the transfer was made. +echo "* Waiting 2 seconds" +sleep 2 +echo "" + +echo "* Get balance of Alice's incognito account (on worker 1)" +ALICE_BALANCE=$(${CLIENTWORKER1} trusted --mrenclave ${MRENCLAVE} balance ${ICGACCOUNTALICE} | xargs) +echo "$ALICE_BALANCE" +echo "" + +echo "* Get balance of Bob's incognito account (on worker 1)" +BOB_BALANCE=$(${CLIENTWORKER1} trusted --mrenclave ${MRENCLAVE} balance ${ICGACCOUNTBOB} | xargs) +echo "$BOB_BALANCE" +echo "" + +ALICE_EXPECTED_BALANCE=10000000000 +BOB_EXPECTED_BALANCE=40000000000 + +echo "* Verifying Alice's balance" +if [ "$ALICE_BALANCE" -ne "$ALICE_EXPECTED_BALANCE" ]; then + echo "Alice's balance is wrong (expected: $ALICE_EXPECTED_BALANCE, actual: $ALICE_BALANCE)" + exit 1 +else + echo "Alice's balance is correct ($ALICE_BALANCE)" +fi +echo "" + +echo "* Verifying Bob's balance" +if [ "$BOB_BALANCE" -ne "$BOB_EXPECTED_BALANCE" ]; then + echo "Bob's balance is wrong (expected: $BOB_EXPECTED_BALANCE, actual: $BOB_BALANCE)" + exit 1 +else + echo "Bob's balance is correct ($BOB_BALANCE)" +fi +echo "" + +exit 0 \ No newline at end of file diff --git a/tee-worker/cli/demo_smart_contract.sh b/tee-worker/cli/demo_smart_contract.sh new file mode 100755 index 0000000000..e04b6e4dff --- /dev/null +++ b/tee-worker/cli/demo_smart_contract.sh @@ -0,0 +1,104 @@ +#!/bin/bash + +# Deploys a simple counter smart contract on our EVM sidechain and increments the value. +# +# setup: +# run all on localhost: +# integritee-node purge-chain --dev +# integritee-node --tmp --dev -lruntime=debug +# export RUST_LOG=integritee_service=info,ita_stf=debug +# integritee-service run +# +# then run this script + +# usage: +# export RUST_LOG_LOG=integritee-cli=info,ita_stf=info +# demo_smart_contract.sh -p -P + +while getopts ":p:A:u:V:C:" opt; do + case $opt in + p) + NPORT=$OPTARG + ;; + A) + WORKERPORT=$OPTARG + ;; + u) + NODEURL=$OPTARG + ;; + V) + WORKERURL=$OPTARG + ;; + C) + CLIENT_BIN=$OPTARG + ;; + esac +done + +# Bytecode from Counter.sol with slightly modified values +SMARTCONTRACT="608060405234801561001057600080fd5b50602260008190555033600160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550610378806100696000396000f3fe6080604052600436106100435760003560e01c80631003e2d21461004e57806333cf508014610077578063371303c0146100a257806358992216146100b957610044565b5b6042600081905550005b34801561005a57600080fd5b50610075600480360381019061007091906101e4565b6100e4565b005b34801561008357600080fd5b5061008c610140565b604051610099919061024a565b60405180910390f35b3480156100ae57600080fd5b506100b7610149565b005b3480156100c557600080fd5b506100ce6101a5565b6040516100db919061022f565b60405180910390f35b806000808282546100f59190610265565b9250508190555033600160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555050565b60008054905090565b600160008082825461015b9190610265565b9250508190555033600160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550565b6000600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16905090565b6000813590506101de8161032b565b92915050565b6000602082840312156101fa576101f9610326565b5b6000610208848285016101cf565b91505092915050565b61021a816102bb565b82525050565b610229816102ed565b82525050565b60006020820190506102446000830184610211565b92915050565b600060208201905061025f6000830184610220565b92915050565b6000610270826102ed565b915061027b836102ed565b9250827fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff038211156102b0576102af6102f7565b5b828201905092915050565b60006102c6826102cd565b9050919050565b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b6000819050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b600080fd5b610334816102ed565b811461033f57600080fd5b5056fea26469706673582212206242c58933a5e80fcfdd7f0044569af44caa21c61740067483a287cc361fc5b464736f6c63430008070033" +INCFUNTION="371303c0" +DEFAULTFUNCTION="371303c1" +ADDFUNCTION="1003e2d20000000000000000000000000000000000000000000000000000000000000003" + + +# using default port if none given as arguments +NPORT=${NPORT:-9944} +NODEURL=${NODEURL:-"ws://127.0.0.1"} + +WORKERPORT=${WORKERPORT:-2000} +WORKERURL=${WORKERURL:-"wss://127.0.0.1"} + + +CLIENT_BIN=${CLIENT_BIN:-"./../bin/integritee-cli"} + +echo "Using client binary ${CLIENT_BIN}" +echo "Using node uri ${NODEURL}:${NPORT}" +echo "Using trusted-worker uri ${WORKERURL}:${WORKERPORT}" + +CLIENTWORKER="${CLIENT_BIN} -p ${NPORT} -P ${WORKERPORT} -u ${NODEURL} -U ${WORKERURL}" + + +# this will always take the first MRENCLAVE found in the registry !! +read -r MRENCLAVE <<< "$($CLIENTWORKER list-workers | awk '/ MRENCLAVE: / { print $2; exit }')" +echo "Reading MRENCLAVE from worker list: ${MRENCLAVE}" + +ACCOUNTALICE=//Alice + +echo "Create smart contract" +${CLIENTWORKER} trusted --mrenclave ${MRENCLAVE} --direct evm-create ${ACCOUNTALICE} ${SMARTCONTRACT} +echo "" + +echo "Get storage" +${CLIENTWORKER} trusted --mrenclave ${MRENCLAVE} evm-read ${ACCOUNTALICE} 0x8a50db1e0f9452cfd91be8dc004ceb11cb08832f +echo "" + +echo "Call inc function" +${CLIENTWORKER} trusted --mrenclave ${MRENCLAVE} --direct evm-call ${ACCOUNTALICE} 0x8a50db1e0f9452cfd91be8dc004ceb11cb08832f ${INCFUNTION} +echo "" + +echo "Get storage" +${CLIENTWORKER} trusted --mrenclave ${MRENCLAVE} evm-read ${ACCOUNTALICE} 0x8a50db1e0f9452cfd91be8dc004ceb11cb08832f +echo "" + +echo "Call add 3 function" +${CLIENTWORKER} trusted --mrenclave ${MRENCLAVE} --direct evm-call ${ACCOUNTALICE} 0x8a50db1e0f9452cfd91be8dc004ceb11cb08832f ${ADDFUNCTION} +echo "" + +echo "Get storage" +RESULT=$(${CLIENTWORKER} trusted --mrenclave ${MRENCLAVE} evm-read ${ACCOUNTALICE} 0x8a50db1e0f9452cfd91be8dc004ceb11cb08832f | xargs) +echo $RESULT +echo "" + +EXPECTED_RETURN_VALUE="0x0000000000000000000000000000000000000000000000000000000000000026" + +echo "* Verifying correct return value" +if (("$RESULT" == "$EXPECTED_RETURN_VALUE")); then + echo "Smart contract return value is correct ($RESULT)" + exit 0 +else + echo "Smart contract return value is wrong (expected: $EXPECTED_RETURN_VALUE, actual: $RESULT)" + exit 1 +fi + +exit 0 diff --git a/tee-worker/cli/demo_teeracle_whitelist.sh b/tee-worker/cli/demo_teeracle_whitelist.sh new file mode 100755 index 0000000000..8e13fd6cc6 --- /dev/null +++ b/tee-worker/cli/demo_teeracle_whitelist.sh @@ -0,0 +1,153 @@ +#!/bin/bash +set -euo pipefail + +trap "echo The demo is terminated (SIGINT); exit 1" SIGINT +trap "echo The demo is terminated (SIGTERM); exit 1" SIGTERM + +# Registers a teeracle with the parentchain, and publish some oracle data. +# +# Demo to show that an enclave can update the exchange rate only when +# 1. the enclave is registered at the pallet-teerex. +# 2. and that the code used is reliable -> the enclave has been put the teeracle whitelist via a governance or sudo +# call. +# +# The teeracle's whitelist has to be empty at the start. So the script needs to run with a clean node state. +# A registered mrenclave will be added in the whitelist by a sudo account. Here //Alice +# +# setup: +# run all on localhost: +# integritee-node purge-chain --dev +# integritee-node --dev -lpallet_teeracle=debug,parity_ws=error,aura=error,sc_basic_authorship=error +# integritee-service --clean-reset run (--skip-ra --dev) +# +# then run this script +# +# usage: +# demo_teeracle_whitelist.sh -p -P -d -i -u -V -C + +while getopts ":p:P:d:i:u:V:C:" opt; do + case $opt in + p) + NPORT=$OPTARG + ;; + P) + WORKER1PORT=$OPTARG + ;; + d) + DURATION=$OPTARG + ;; + i) + INTERVAL=$OPTARG + ;; + u) + NODEURL=$OPTARG + ;; + V) + WORKER1URL=$OPTARG + ;; + C) + CLIENT_BIN=$OPTARG + ;; + esac +done + +# using default port if none given as arguments +NPORT=${NPORT:-9944} +NODEURL=${NODEURL:-"ws://127.0.0.1"} + +WORKER1PORT=${WORKER1PORT:-2000} +WORKER1URL=${WORKER1URL:-"wss://127.0.0.1"} + +CLIENT_BIN=${CLIENT_BIN:-"./../bin/integritee-cli"} + +DURATION=${DURATION:-48} +INTERVAL=${INTERVAL:-86400} + +LISTEN_TO_EXCHANGE_RATE_EVENTS_CMD="exchange-oracle listen-to-exchange-rate-events" +ADD_TO_WHITELIST_CMD="exchange-oracle add-to-whitelist" + +echo "Using client binary ${CLIENT_BIN}" +echo "Using node uri ${NODEURL}:${NPORT}" +echo "Using trusted-worker uri ${WORKER1URL}:${WORKER1PORT}" +echo "Using worker market data update interval ${INTERVAL}" +echo "Count the update events for ${DURATION}" +echo "" + +COIN_GECKO="https://api.coingecko.com/" +COIN_MARKET_CAP="https://pro-api.coinmarketcap.com/" +let "MIN_EXPECTED_NUM_OF_EVENTS=$DURATION/$INTERVAL-2" +echo "Minimum expected number of events with a single oracle source: ${MIN_EXPECTED_NUM_OF_EVENTS}" + +let "MIN_EXPECTED_NUM_OF_EVENTS_2 = 2*$MIN_EXPECTED_NUM_OF_EVENTS" +echo "Minimum expected number of events with two oracle sources: ${MIN_EXPECTED_NUM_OF_EVENTS_2}" + +CLIENT="${CLIENT_BIN} -p ${NPORT} -P ${WORKER1PORT} -u ${NODEURL} -U ${WORKER1URL}" + +echo "* Query on-chain enclave registry:" +${CLIENT} list-workers +echo "" + +# this will always take the first MRENCLAVE found in the registry !! +read MRENCLAVE <<< $($CLIENT list-workers | awk '/ MRENCLAVE: / { print $2; exit }') +echo "Reading MRENCLAVE from worker list: ${MRENCLAVE}" + +[[ -z $MRENCLAVE ]] && { echo "MRENCLAVE is empty. cannot continue" ; exit 1; } +echo "" + +echo "Listen to ExchangeRateUpdated events for ${DURATION} seconds. There should be no trusted oracle source!" +#${CLIENT} ${LISTEN_TO_EXCHANGE_RATE_EVENTS_CMD} ${DURATION} +#echo "" + +read NO_EVENTS <<< $(${CLIENT} ${LISTEN_TO_EXCHANGE_RATE_EVENTS_CMD} ${DURATION} | awk '/ EVENTS_COUNT: / { print $2; exit }') +echo "Got ${NO_EVENTS} exchange rate updates when no trusted oracle source is in the whitelist" +echo "" + +echo "Add ${COIN_GECKO} for ${MRENCLAVE} as trusted oracle source" +${CLIENT} ${ADD_TO_WHITELIST_CMD} //Alice ${COIN_GECKO} ${MRENCLAVE} +echo "MRENCLAVE in whitelist for ${COIN_GECKO}" +echo "" + +echo "Listen to ExchangeRateUpdated events for ${DURATION} seconds, after a trusted oracle source has been added to the whitelist." +#${CLIENT} ${LISTEN_TO_EXCHANGE_RATE_EVENTS_CMD} ${DURATION} +#echo "" + +read EVENTS_COUNT <<< $($CLIENT ${LISTEN_TO_EXCHANGE_RATE_EVENTS_CMD} ${DURATION} | awk '/ EVENTS_COUNT: / { print $2; exit }') +echo "Got ${EVENTS_COUNT} exchange rate updates from the trusted oracle source in ${DURATION} second(s)" +echo "" + +echo "Add ${COIN_MARKET_CAP} for ${MRENCLAVE} as trusted oracle source" +${CLIENT} ${ADD_TO_WHITELIST_CMD} //Alice ${COIN_MARKET_CAP} ${MRENCLAVE} +echo "MRENCLAVE in whitelist for ${COIN_MARKET_CAP}" +echo "" + +echo "Listen to ExchangeRateUpdated events for ${DURATION} seconds, after a second trusted oracle source has been added to the whitelist." +#${CLIENT} ${LISTEN_TO_EXCHANGE_RATE_EVENTS_CMD} ${DURATION} +#echo "" + +read EVENTS_COUNT_2 <<< $($CLIENT ${LISTEN_TO_EXCHANGE_RATE_EVENTS_CMD} ${DURATION} | awk '/ EVENTS_COUNT: / { print $2; exit }') +echo "Got ${EVENTS_COUNT_2} exchange rate updates from 2 trusted oracle sources in ${DURATION} second(s)" +echo "" + +echo "Results :" + +# the following test is for automated CI +# it only works if the teeracle's whitelist is empty at the start (run it from genesis) +if [ $EVENTS_COUNT_2 -ge $MIN_EXPECTED_NUM_OF_EVENTS_2 ]; then + if [ $EVENTS_COUNT -ge $MIN_EXPECTED_NUM_OF_EVENTS ]; then + if [ 0 -eq $NO_EVENTS ]; then + echo "test passed" + exit 0 + else + echo "The test ran through but we received ExchangeRateUpdated events before the enclave was added to the whitelist. Was the enclave previously whitelisted? Perhaps by another teeracle?" + exit 1 + fi + else + echo "test failed: Not enough events received for single oracle source: $EVENTS_COUNT. Should be greater than $MIN_EXPECTED_NUM_OF_EVENTS" + exit 1 + fi +else + echo "test failed: Not enough events received for 2 oracle sources: $EVENTS_COUNT_2. Should be greater than $MIN_EXPECTED_NUM_OF_EVENTS_2" + exit 1 +fi + +exit 1 diff --git a/tee-worker/cli/src/base_cli/commands/balance.rs b/tee-worker/cli/src/base_cli/commands/balance.rs new file mode 100644 index 0000000000..74ad9264cd --- /dev/null +++ b/tee-worker/cli/src/base_cli/commands/balance.rs @@ -0,0 +1,37 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +use crate::{ + command_utils::{get_accountid_from_str, get_chain_api}, + Cli, +}; + +#[derive(Parser)] +pub struct BalanceCommand { + /// AccountId in ss58check format + account: String, +} + +impl BalanceCommand { + pub(crate) fn run(&self, cli: &Cli) { + let api = get_chain_api(cli); + let accountid = get_accountid_from_str(&self.account); + let balance = + if let Some(data) = api.get_account_data(&accountid).unwrap() { data.free } else { 0 }; + println!("{}", balance); + } +} diff --git a/tee-worker/cli/src/base_cli/commands/faucet.rs b/tee-worker/cli/src/base_cli/commands/faucet.rs new file mode 100644 index 0000000000..c432318763 --- /dev/null +++ b/tee-worker/cli/src/base_cli/commands/faucet.rs @@ -0,0 +1,57 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +use crate::{ + command_utils::{get_accountid_from_str, get_chain_api}, + Cli, +}; +use my_node_runtime::{BalancesCall, Call}; +use sp_keyring::AccountKeyring; +use std::vec::Vec; +use substrate_api_client::{compose_extrinsic_offline, UncheckedExtrinsicV4, XtStatus}; + +const PREFUNDING_AMOUNT: u128 = 1_000_000_000; + +#[derive(Parser)] +pub struct FaucetCommand { + /// Account(s) to be funded, ss58check encoded + #[clap(min_values = 1, required = true)] + accounts: Vec, +} + +impl FaucetCommand { + pub(crate) fn run(&self, cli: &Cli) { + let api = get_chain_api(cli).set_signer(AccountKeyring::Alice.pair()); + let mut nonce = api.get_nonce().unwrap(); + for account in &self.accounts { + let to = get_accountid_from_str(account); + #[allow(clippy::redundant_clone)] + let xt: UncheckedExtrinsicV4<_, _> = compose_extrinsic_offline!( + api.clone().signer.unwrap(), + Call::Balances(BalancesCall::transfer { + dest: GenericAddress::Id(to.clone()), + value: PREFUNDING_AMOUNT + }), + api.extrinsic_params(nonce) + ); + // send and watch extrinsic until finalized + println!("Faucet drips to {} (Alice's nonce={})", to, nonce); + let _blockh = api.send_extrinsic(xt.hex_encode(), XtStatus::Ready).unwrap(); + nonce += 1; + } + } +} diff --git a/tee-worker/cli/src/base_cli/commands/listen.rs b/tee-worker/cli/src/base_cli/commands/listen.rs new file mode 100644 index 0000000000..42ecc8095a --- /dev/null +++ b/tee-worker/cli/src/base_cli/commands/listen.rs @@ -0,0 +1,152 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +use crate::{command_utils::get_chain_api, Cli}; +use base58::ToBase58; +use codec::{Decode, Encode}; +use log::*; +use my_node_runtime::{Event, Hash}; +use std::{sync::mpsc::channel, vec::Vec}; +use substrate_api_client::utils::FromHexString; + +#[derive(Parser)] +pub struct ListenCommand { + /// exit after given number of parentchain events + #[clap(short, long = "exit-after")] + events: Option, + + /// exit after given number of blocks + #[clap(short, long = "await-blocks")] + blocks: Option, +} + +impl ListenCommand { + pub(crate) fn run(&self, cli: &Cli) { + println!("{:?} {:?}", self.events, self.blocks); + let api = get_chain_api(cli); + info!("Subscribing to events"); + let (events_in, events_out) = channel(); + let mut count = 0u32; + let mut blocks = 0u32; + api.subscribe_events(events_in).unwrap(); + loop { + if let Some(e) = self.events { + if count >= e { + return + } + }; + if let Some(b) = self.blocks { + if blocks >= b { + return + } + }; + let event_str = events_out.recv().unwrap(); + let _unhex = Vec::from_hex(event_str).unwrap(); + let mut _er_enc = _unhex.as_slice(); + let _events = Vec::>::decode(&mut _er_enc); + blocks += 1; + match _events { + Ok(evts) => + for evr in &evts { + println!("decoded: phase {:?} event {:?}", evr.phase, evr.event); + match &evr.event { + Event::Balances(be) => { + println!(">>>>>>>>>> balances event: {:?}", be); + match &be { + pallet_balances::Event::Transfer { from, to, amount } => { + println!("From: {:?}", from); + println!("To: {:?}", to); + println!("Value: {:?}", amount); + }, + _ => { + debug!("ignoring unsupported balances event"); + }, + } + }, + Event::Teerex(ee) => { + println!(">>>>>>>>>> integritee event: {:?}", ee); + count += 1; + match &ee { + my_node_runtime::pallet_teerex::Event::AddedEnclave( + accountid, + url, + ) => { + println!( + "AddedEnclave: {:?} at url {}", + accountid, + String::from_utf8(url.to_vec()) + .unwrap_or_else(|_| "error".to_string()) + ); + }, + my_node_runtime::pallet_teerex::Event::RemovedEnclave( + accountid, + ) => { + println!("RemovedEnclave: {:?}", accountid); + }, + my_node_runtime::pallet_teerex::Event::Forwarded(shard) => { + println!( + "Forwarded request for shard {}", + shard.encode().to_base58() + ); + }, + my_node_runtime::pallet_teerex::Event::ProcessedParentchainBlock( + accountid, + block_hash, + merkle_root, + block_number, + ) => { + println!( + "ProcessedParentchainBlock from {} with hash {:?}, number {} and merkle root {:?}", + accountid, block_hash, merkle_root, block_number + ); + }, + my_node_runtime::pallet_teerex::Event::ShieldFunds( + incognito_account, + ) => { + println!("ShieldFunds for {:?}", incognito_account); + }, + my_node_runtime::pallet_teerex::Event::UnshieldedFunds( + public_account, + ) => { + println!("UnshieldFunds for {:?}", public_account); + }, + _ => debug!("ignoring unsupported teerex event: {:?}", ee), + } + }, + Event::Sidechain(ee) => { + count += 1; + match &ee { + my_node_runtime::pallet_sidechain::Event::ProposedSidechainBlock( + accountid, + block_hash, + ) => { + println!( + "ProposedSidechainBlock from {} with hash {:?}", + accountid, block_hash + ); + }, + _ => debug!("ignoring unsupported sidechain event: {:?}", ee), + } + }, + _ => debug!("ignoring unsupported module event: {:?}", evr.event), + } + }, + Err(_) => error!("couldn't decode event record list"), + } + } + } +} diff --git a/tee-worker/cli/src/base_cli/commands/litentry/link_identity.rs b/tee-worker/cli/src/base_cli/commands/litentry/link_identity.rs new file mode 100644 index 0000000000..3fb33b8c28 --- /dev/null +++ b/tee-worker/cli/src/base_cli/commands/litentry/link_identity.rs @@ -0,0 +1,74 @@ +// Copyright 2020-2022 Litentry Technologies GmbH. +// This file is part of Litentry. +// +// Litentry is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Litentry is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Litentry. If not, see . + +use super::IMP; +use crate::{ + command_utils::{get_chain_api, *}, + Cli, +}; +use base58::FromBase58; +use codec::{Decode, Encode}; +use ita_stf::ShardIdentifier; +use itp_sgx_crypto::ShieldingCryptoEncrypt; +use litentry_primitives::Identity; +use log::*; + +use sp_core::sr25519 as sr25519_core; +use substrate_api_client::{compose_extrinsic, UncheckedExtrinsicV4, XtStatus}; + +#[derive(Parser)] +pub struct LinkIdentityCommand { + /// AccountId in ss58check format + account: String, + /// Identity to link + identity: String, + /// Shard identifier + shard: String, +} + +impl LinkIdentityCommand { + pub(crate) fn run(&self, cli: &Cli) { + let chain_api = get_chain_api(cli); + + let shard_opt = match self.shard.from_base58() { + Ok(s) => ShardIdentifier::decode(&mut &s[..]), + _ => panic!("shard argument must be base58 encoded"), + }; + + let shard = match shard_opt { + Ok(shard) => shard, + Err(e) => panic!("{}", e), + }; + + let who = get_pair_from_str(&self.account); + let chain_api = chain_api.set_signer(sr25519_core::Pair::from(who)); + + let identity: Result = serde_json::from_str(self.identity.as_str()); + if let Err(e) = identity { + warn!("Deserialize Identity error: {:?}", e.to_string()); + return + } + + let tee_shielding_key = get_shielding_key(cli).unwrap(); + let encrypted_identity = tee_shielding_key.encrypt(&identity.unwrap().encode()).unwrap(); + + let xt: UncheckedExtrinsicV4<_, _> = + compose_extrinsic!(chain_api, IMP, "link_identity", shard, encrypted_identity.to_vec()); + + let tx_hash = chain_api.send_extrinsic(xt.hex_encode(), XtStatus::Finalized).unwrap(); + println!("[+] TrustedOperation got finalized. Hash: {:?}\n", tx_hash); + } +} diff --git a/tee-worker/cli/src/base_cli/commands/litentry/mod.rs b/tee-worker/cli/src/base_cli/commands/litentry/mod.rs new file mode 100644 index 0000000000..39f9659221 --- /dev/null +++ b/tee-worker/cli/src/base_cli/commands/litentry/mod.rs @@ -0,0 +1,21 @@ +// Copyright 2020-2022 Litentry Technologies GmbH. +// This file is part of Litentry. +// +// Litentry is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Litentry is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Litentry. If not, see . + +pub mod link_identity; +pub mod set_user_shielding_key; + +// TODO: maybe move it to use itp_node_api::api_client +pub const IMP: &str = "IdentityManagement"; diff --git a/tee-worker/cli/src/base_cli/commands/litentry/set_user_shielding_key.rs b/tee-worker/cli/src/base_cli/commands/litentry/set_user_shielding_key.rs new file mode 100644 index 0000000000..fcad27646a --- /dev/null +++ b/tee-worker/cli/src/base_cli/commands/litentry/set_user_shielding_key.rs @@ -0,0 +1,72 @@ +// Copyright 2020-2022 Litentry Technologies GmbH. +// This file is part of Litentry. +// +// Litentry is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Litentry is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Litentry. If not, see . + +use super::IMP; +use crate::{ + command_utils::{get_chain_api, *}, + Cli, +}; +use base58::FromBase58; +use codec::{Decode, Encode}; +use ita_stf::ShardIdentifier; +use itp_sgx_crypto::ShieldingCryptoEncrypt; +use log::*; + +use sp_core::sr25519 as sr25519_core; +use substrate_api_client::{compose_extrinsic, UncheckedExtrinsicV4, XtStatus}; + +#[derive(Parser)] +pub struct SetUserShieldingKeyCommand { + /// AccountId in ss58check format + account: String, + + /// Shielding key in hex string + key_hex: String, + + /// Shard identifier + shard: String, +} + +impl SetUserShieldingKeyCommand { + pub(crate) fn run(&self, cli: &Cli) { + let chain_api = get_chain_api(cli); + + let shard_opt = match self.shard.from_base58() { + Ok(s) => ShardIdentifier::decode(&mut &s[..]), + _ => panic!("shard argument must be base58 encoded"), + }; + + let shard = match shard_opt { + Ok(shard) => shard, + Err(e) => panic!("{}", e), + }; + + let who = get_pair_from_str(&self.account); + let chain_api = chain_api.set_signer(sr25519_core::Pair::from(who)); + + let mut key = [0u8; 32]; + hex::decode_to_slice(&self.key_hex, &mut key).expect("decoding key failed"); + + let tee_shielding_key = get_shielding_key(cli).unwrap(); + let encrypted_key = tee_shielding_key.encrypt(&key.encode()).unwrap(); + + let xt: UncheckedExtrinsicV4<_, _> = + compose_extrinsic!(chain_api, IMP, "set_user_shielding_key", shard, encrypted_key); + + let tx_hash = chain_api.send_extrinsic(xt.hex_encode(), XtStatus::Finalized).unwrap(); + println!("[+] TrustedOperation got finalized. Hash: {:?}\n", tx_hash); + } +} diff --git a/tee-worker/cli/src/base_cli/commands/mod.rs b/tee-worker/cli/src/base_cli/commands/mod.rs new file mode 100644 index 0000000000..74a17c4452 --- /dev/null +++ b/tee-worker/cli/src/base_cli/commands/mod.rs @@ -0,0 +1,6 @@ +pub mod balance; +pub mod faucet; +pub mod listen; +pub mod litentry; +pub mod shield_funds; +pub mod transfer; diff --git a/tee-worker/cli/src/base_cli/commands/shield_funds.rs b/tee-worker/cli/src/base_cli/commands/shield_funds.rs new file mode 100644 index 0000000000..8cf4becd5c --- /dev/null +++ b/tee-worker/cli/src/base_cli/commands/shield_funds.rs @@ -0,0 +1,84 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +use crate::{ + command_utils::{get_accountid_from_str, get_chain_api, *}, + Cli, +}; +use base58::FromBase58; +use codec::{Decode, Encode}; +use ita_stf::ShardIdentifier; +use itp_node_api::api_client::TEEREX; +use itp_sgx_crypto::ShieldingCryptoEncrypt; +use log::*; +use my_node_runtime::Balance; +use sp_core::sr25519 as sr25519_core; +use substrate_api_client::{compose_extrinsic, UncheckedExtrinsicV4, XtStatus}; + +#[derive(Parser)] +pub struct ShieldFundsCommand { + /// Sender's parentchain AccountId in ss58check format + from: String, + + /// Recipient's incognito AccountId in ss58check format + to: String, + + /// Amount to be transferred + amount: Balance, + + /// Shard identifier + shard: String, +} + +impl ShieldFundsCommand { + pub(crate) fn run(&self, cli: &Cli) { + let chain_api = get_chain_api(cli); + + let shard_opt = match self.shard.from_base58() { + Ok(s) => ShardIdentifier::decode(&mut &s[..]), + _ => panic!("shard argument must be base58 encoded"), + }; + + let shard = match shard_opt { + Ok(shard) => shard, + Err(e) => panic!("{}", e), + }; + + // get the sender + let from = get_pair_from_str(&self.from); + let chain_api = chain_api.set_signer(sr25519_core::Pair::from(from)); + + // get the recipient + let to = get_accountid_from_str(&self.to); + + let encryption_key = get_shielding_key(cli).unwrap(); + let encrypted_recevier = encryption_key.encrypt(&to.encode()).unwrap(); + + // compose the extrinsic + let xt: UncheckedExtrinsicV4<_, _> = compose_extrinsic!( + chain_api, + TEEREX, + "shield_funds", + encrypted_recevier, + self.amount, + shard + ); + + let tx_hash = chain_api.send_extrinsic(xt.hex_encode(), XtStatus::Finalized).unwrap(); + println!("[+] TrustedOperation got finalized. Hash: {:?}\n", tx_hash); + } +} diff --git a/tee-worker/cli/src/base_cli/commands/transfer.rs b/tee-worker/cli/src/base_cli/commands/transfer.rs new file mode 100644 index 0000000000..781d9407e8 --- /dev/null +++ b/tee-worker/cli/src/base_cli/commands/transfer.rs @@ -0,0 +1,52 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +use crate::{ + command_utils::{get_accountid_from_str, get_chain_api, *}, + Cli, +}; +use log::*; +use my_node_runtime::Balance; +use sp_core::{crypto::Ss58Codec, sr25519 as sr25519_core, Pair}; +use substrate_api_client::{GenericAddress, XtStatus}; + +#[derive(Parser)] +pub struct TransferCommand { + /// sender's AccountId in ss58check format + from: String, + + /// recipient's AccountId in ss58check format + to: String, + + /// amount to be transferred + amount: Balance, +} + +impl TransferCommand { + pub(crate) fn run(&self, cli: &Cli) { + let from_account = get_pair_from_str(&self.from); + let to_account = get_accountid_from_str(&self.to); + info!("from ss58 is {}", from_account.public().to_ss58check()); + info!("to ss58 is {}", to_account.to_ss58check()); + let api = get_chain_api(cli).set_signer(sr25519_core::Pair::from(from_account)); + let xt = api.balance_transfer(GenericAddress::Id(to_account.clone()), self.amount); + let tx_hash = api.send_extrinsic(xt.hex_encode(), XtStatus::InBlock).unwrap(); + println!("[+] TrustedOperation got finalized. Hash: {:?}\n", tx_hash); + let result = api.get_account_data(&to_account).unwrap().unwrap(); + println!("balance for {} is now {}", to_account, result.free); + } +} diff --git a/tee-worker/cli/src/base_cli/mod.rs b/tee-worker/cli/src/base_cli/mod.rs new file mode 100644 index 0000000000..e8028918ea --- /dev/null +++ b/tee-worker/cli/src/base_cli/mod.rs @@ -0,0 +1,157 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +use crate::{ + base_cli::commands::{ + balance::BalanceCommand, + faucet::FaucetCommand, + listen::ListenCommand, + litentry::{ + link_identity::LinkIdentityCommand, set_user_shielding_key::SetUserShieldingKeyCommand, + }, + shield_funds::ShieldFundsCommand, + transfer::TransferCommand, + }, + command_utils::*, + Cli, +}; +use base58::ToBase58; +use chrono::{DateTime, Utc}; +use clap::Subcommand; +use itc_rpc_client::direct_client::DirectApi; +use itp_node_api::api_client::PalletTeerexApi; +use sp_application_crypto::{ed25519, sr25519}; +use sp_core::{crypto::Ss58Codec, Pair}; +use std::{ + path::PathBuf, + time::{Duration, UNIX_EPOCH}, +}; +use substrate_api_client::Metadata; +use substrate_client_keystore::{KeystoreExt, LocalKeystore}; + +mod commands; + +#[derive(Subcommand)] +pub enum BaseCli { + /// query parentchain balance for AccountId + Balance(BalanceCommand), + + /// generates a new account for the integritee chain in your local keystore + NewAccount, + + /// lists all accounts in your local keystore for the integritee chain + ListAccounts, + + /// query node metadata and print it as json to stdout + PrintMetadata, + + /// query sgx-runtime metadata and print it as json to stdout + PrintSgxMetadata, + + /// send some bootstrapping funds to supplied account(s) + Faucet(FaucetCommand), + + /// transfer funds from one parentchain account to another + Transfer(TransferCommand), + + /// query enclave registry and list all workers + ListWorkers, + + /// listen to parentchain events + Listen(ListenCommand), + + /// Transfer funds from an parentchain account to an incognito account + ShieldFunds(ShieldFundsCommand), + + // Litentry's commands below + /// set the user's shielding key + SetUserShieldingKey(SetUserShieldingKeyCommand), + + LinkIdentity(LinkIdentityCommand), +} + +impl BaseCli { + pub fn run(&self, cli: &Cli) { + match self { + BaseCli::Balance(cmd) => cmd.run(cli), + BaseCli::NewAccount => new_account(), + BaseCli::ListAccounts => list_accounts(), + BaseCli::PrintMetadata => print_metadata(cli), + BaseCli::PrintSgxMetadata => print_sgx_metadata(cli), + BaseCli::Faucet(cmd) => cmd.run(cli), + BaseCli::Transfer(cmd) => cmd.run(cli), + BaseCli::ListWorkers => list_workers(cli), + BaseCli::Listen(cmd) => cmd.run(cli), + BaseCli::ShieldFunds(cmd) => cmd.run(cli), + // Litentry's commands below + BaseCli::SetUserShieldingKey(cmd) => cmd.run(cli), + BaseCli::LinkIdentity(cmd) => cmd.run(cli), + } + } +} + +fn new_account() { + let store = LocalKeystore::open(PathBuf::from(&KEYSTORE_PATH), None).unwrap(); + let key: sr25519::AppPair = store.generate().unwrap(); + drop(store); + println!("{}", key.public().to_ss58check()); +} + +fn list_accounts() { + let store = LocalKeystore::open(PathBuf::from(&KEYSTORE_PATH), None).unwrap(); + println!("sr25519 keys:"); + for pubkey in store.public_keys::().unwrap().into_iter() { + println!("{}", pubkey.to_ss58check()); + } + println!("ed25519 keys:"); + for pubkey in store.public_keys::().unwrap().into_iter() { + println!("{}", pubkey.to_ss58check()); + } + drop(store); +} + +fn print_metadata(cli: &Cli) { + let meta = get_chain_api(cli).get_metadata().unwrap(); + println!("Metadata:\n {}", Metadata::pretty_format(&meta).unwrap()); +} + +fn print_sgx_metadata(cli: &Cli) { + let worker_api_direct = get_worker_api_direct(cli); + let metadata = worker_api_direct.get_state_metadata().unwrap(); + println!("Metadata:\n {}", Metadata::pretty_format(&metadata).unwrap()); +} + +fn list_workers(cli: &Cli) { + let api = get_chain_api(cli); + let wcount = api.enclave_count(None).unwrap(); + println!("number of workers registered: {}", wcount); + for w in 1..=wcount { + let enclave = api.enclave(w, None).unwrap(); + if enclave.is_none() { + println!("error reading enclave data"); + continue + }; + let enclave = enclave.unwrap(); + let timestamp = + DateTime::::from(UNIX_EPOCH + Duration::from_millis(enclave.timestamp)); + println!("Enclave {}", w); + println!(" AccountId: {}", enclave.pubkey.to_ss58check()); + println!(" MRENCLAVE: {}", enclave.mr_enclave.to_base58()); + println!(" RA timestamp: {}", timestamp); + println!(" URL: {}", enclave.url); + } +} diff --git a/tee-worker/cli/src/benchmark/mod.rs b/tee-worker/cli/src/benchmark/mod.rs new file mode 100644 index 0000000000..e1081b4d3f --- /dev/null +++ b/tee-worker/cli/src/benchmark/mod.rs @@ -0,0 +1,369 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +use crate::{ + command_utils::get_worker_api_direct, + get_layer_two_nonce, + trusted_command_utils::{ + decode_balance, get_identifiers, get_keystore_path, get_pair_from_str, + }, + trusted_commands::TrustedArgs, + trusted_operation::{get_json_request, get_state, perform_trusted_operation, wait_until}, + Cli, +}; +use codec::Decode; +use hdrhistogram::Histogram; +use ita_stf::{Getter, Index, KeyPair, TrustedCall, TrustedGetter, TrustedOperation}; +use itc_rpc_client::direct_client::{DirectApi, DirectClient}; +use itp_types::{ + Balance, ShardIdentifier, TrustedOperationStatus, + TrustedOperationStatus::{InSidechainBlock, Submitted}, +}; +use log::*; +use rand::Rng; +use rayon::prelude::*; +use sgx_crypto_helper::rsa3072::Rsa3072PubKey; +use sp_application_crypto::sr25519; +use sp_core::{sr25519 as sr25519_core, Pair}; +use std::{ + boxed::Box, + string::ToString, + sync::mpsc::{channel, Receiver}, + thread, time, + time::Instant, + vec::Vec, +}; +use substrate_client_keystore::{KeystoreExt, LocalKeystore}; + +// Needs to be above the existential deposit minimum, otherwise an account will not +// be created and the state is not increased. +const EXISTENTIAL_DEPOSIT: Balance = 1000; + +#[derive(Parser)] +pub struct BenchmarkCommands { + /// The number of clients (=threads) to be used in the benchmark + #[clap(default_value_t = 10)] + number_clients: u32, + + /// The number of iterations to execute for each client + #[clap(default_value_t = 30)] + number_iterations: u32, + + /// Adds a random wait before each transaction. This is the lower bound for the interval in ms. + #[clap(default_value_t = 0)] + random_wait_before_transaction_min_ms: u32, + + /// Adds a random wait before each transaction. This is the upper bound for the interval in ms. + #[clap(default_value_t = 0)] + random_wait_before_transaction_max_ms: u32, + + /// Whether to wait for "InSidechainBlock" confirmation for each transaction + #[clap(short, long)] + wait_for_confirmation: bool, + + /// Account to be used for initial funding of generated accounts used in benchmark + #[clap(default_value_t = String::from("//Alice"))] + funding_account: String, +} + +struct BenchmarkClient { + account: sr25519_core::Pair, + current_balance: u128, + client_api: DirectClient, + receiver: Receiver, +} + +impl BenchmarkClient { + fn new( + account: sr25519_core::Pair, + initial_balance: u128, + initial_request: String, + cli: &Cli, + ) -> Self { + debug!("get direct api"); + let client_api = get_worker_api_direct(cli); + + debug!("setup sender and receiver"); + let (sender, receiver) = channel(); + client_api.watch(initial_request, sender); + BenchmarkClient { account, current_balance: initial_balance, client_api, receiver } + } +} + +/// Stores timing information about a specific transaction +struct BenchmarkTransaction { + started: Instant, + submitted: Instant, + confirmed: Option, +} + +impl BenchmarkCommands { + pub(crate) fn run(&self, cli: &Cli, trusted_args: &TrustedArgs) { + let random_wait_before_transaction_ms: (u32, u32) = ( + self.random_wait_before_transaction_min_ms, + self.random_wait_before_transaction_max_ms, + ); + let store = LocalKeystore::open(get_keystore_path(trusted_args), None).unwrap(); + let funding_account_keys = get_pair_from_str(trusted_args, &self.funding_account); + + let (mrenclave, shard) = get_identifiers(trusted_args); + + // Get shielding pubkey. + let worker_api_direct = get_worker_api_direct(cli); + let shielding_pubkey: Rsa3072PubKey = match worker_api_direct.get_rsa_pubkey() { + Ok(key) => key, + Err(err_msg) => panic!("{}", err_msg.to_string()), + }; + + let nonce_start = get_layer_two_nonce!(funding_account_keys, cli, trusted_args); + println!("Nonce for account {}: {}", self.funding_account, nonce_start); + + let mut accounts = Vec::new(); + + // Setup new accounts and initialize them with money from Alice. + for i in 0..self.number_clients { + let nonce = i + nonce_start; + println!("Initializing account {}", i); + + // Create new account to use. + let a: sr25519::AppPair = store.generate().unwrap(); + let account = get_pair_from_str(trusted_args, a.public().to_string().as_str()); + let initial_balance = 10000000; + + // Transfer amount from Alice to new account. + let top: TrustedOperation = TrustedCall::balance_transfer( + funding_account_keys.public().into(), + account.public().into(), + initial_balance, + ) + .sign( + &KeyPair::Sr25519(Box::new(funding_account_keys.clone())), + nonce, + &mrenclave, + &shard, + ) + .into_trusted_operation(trusted_args.direct); + + // For the last account we wait for confirmation in order to ensure all accounts were setup correctly + let wait_for_confirmation = i == self.number_clients - 1; + let account_funding_request = get_json_request(shard, &top, shielding_pubkey); + + let client = + BenchmarkClient::new(account, initial_balance, account_funding_request, cli); + let _result = wait_for_top_confirmation(wait_for_confirmation, &client); + accounts.push(client); + } + + rayon::ThreadPoolBuilder::new() + .num_threads(self.number_clients as usize) + .build_global() + .unwrap(); + + let overall_start = Instant::now(); + + // Run actual benchmark logic, in parallel, for each account initialized above. + let outputs: Vec> = accounts + .into_par_iter() + .map(move |mut client| { + let mut output: Vec = Vec::new(); + + for i in 0..self.number_iterations { + println!("Iteration: {}", i); + + if random_wait_before_transaction_ms.1 > 0 { + random_wait(random_wait_before_transaction_ms); + } + + // Create new account. + let account_keys: sr25519::AppPair = store.generate().unwrap(); + let new_account = + get_pair_from_str(trusted_args, account_keys.public().to_string().as_str()); + + + println!(" Transfer amount: {}", EXISTENTIAL_DEPOSIT); + println!(" From: {:?}", client.account.public()); + println!(" To: {:?}", new_account.public()); + + // Get nonce of account. + let nonce = get_nonce(client.account.clone(), shard, &client.client_api); + + // Transfer money from client account to new account. + let top: TrustedOperation = TrustedCall::balance_transfer( + client.account.public().into(), + new_account.public().into(), + EXISTENTIAL_DEPOSIT, + ) + .sign(&KeyPair::Sr25519(Box::new(client.account.clone())), nonce, &mrenclave, &shard) + .into_trusted_operation(trusted_args.direct); + + let last_iteration = i == self.number_iterations - 1; + let jsonrpc_call = get_json_request(shard, &top, shielding_pubkey); + client.client_api.send(&jsonrpc_call).unwrap(); + let result = wait_for_top_confirmation( + self.wait_for_confirmation || last_iteration, + &client, + ); + + client.current_balance -= EXISTENTIAL_DEPOSIT; + + let balance = get_balance(client.account.clone(), shard, &client.client_api); + println!("Balance: {}", balance.unwrap_or_default()); + assert_eq!(client.current_balance, balance.unwrap()); + + output.push(result); + + // FIXME: We probably should re-fund the account in this case. + if client.current_balance <= EXISTENTIAL_DEPOSIT { + error!("Account {:?} does not have enough balance anymore. Finishing benchmark early", client.account.public()); + break; + } + } + + client.client_api.close().unwrap(); + + output + }) + .collect(); + + println!( + "Finished benchmark with {} clients and {} transactions in {} ms", + self.number_clients, + self.number_iterations, + overall_start.elapsed().as_millis() + ); + + print_benchmark_statistic(outputs, self.wait_for_confirmation) + } +} + +fn get_balance( + account: sr25519::Pair, + shard: ShardIdentifier, + direct_client: &DirectClient, +) -> Option { + let getter = Getter::trusted( + TrustedGetter::free_balance(account.public().into()) + .sign(&KeyPair::Sr25519(Box::new(account.clone()))), + ); + + let getter_start_timer = Instant::now(); + let getter_result = get_state(direct_client, shard, &getter); + let getter_execution_time = getter_start_timer.elapsed().as_millis(); + + let balance = decode_balance(getter_result); + info!("Balance getter execution took {} ms", getter_execution_time,); + debug!("Retrieved {:?} Balance for {:?}", balance.unwrap_or_default(), account.public()); + balance +} + +fn get_nonce( + account: sr25519::Pair, + shard: ShardIdentifier, + direct_client: &DirectClient, +) -> Index { + let getter = Getter::trusted( + TrustedGetter::nonce(account.public().into()) + .sign(&KeyPair::Sr25519(Box::new(account.clone()))), + ); + + let getter_start_timer = Instant::now(); + let getter_result = get_state(direct_client, shard, &getter); + let getter_execution_time = getter_start_timer.elapsed().as_millis(); + + let nonce = match getter_result { + Some(encoded_nonce) => Index::decode(&mut encoded_nonce.as_slice()).unwrap(), + None => Default::default(), + }; + info!("Nonce getter execution took {} ms", getter_execution_time,); + debug!("Retrieved {:?} nonce for {:?}", nonce, account.public()); + nonce +} + +fn print_benchmark_statistic(outputs: Vec>, wait_for_confirmation: bool) { + let mut hist = Histogram::::new(1).unwrap(); + for output in outputs { + for t in output { + let benchmarked_timestamp = + if wait_for_confirmation { t.confirmed } else { Some(t.submitted) }; + if let Some(confirmed) = benchmarked_timestamp { + hist += confirmed.duration_since(t.started).as_millis() as u64; + } else { + println!("Missing measurement data"); + } + } + } + + for i in (5..=100).step_by(5) { + let text = format!( + "{} percent are done within {} ms", + i, + hist.value_at_quantile(i as f64 / 100.0) + ); + println!("{}", text); + } +} + +fn random_wait(random_wait_before_transaction_ms: (u32, u32)) { + let mut rng = rand::thread_rng(); + let sleep_time = time::Duration::from_millis( + rng.gen_range(random_wait_before_transaction_ms.0..=random_wait_before_transaction_ms.1) + .into(), + ); + println!("Sleep for: {}ms", sleep_time.as_millis()); + thread::sleep(sleep_time); +} + +fn wait_for_top_confirmation( + wait_for_sidechain_block: bool, + client: &BenchmarkClient, +) -> BenchmarkTransaction { + let started = Instant::now(); + + let submitted = wait_until(&client.receiver, is_submitted); + + let confirmed = if wait_for_sidechain_block { + // We wait for the transaction hash that actually matches the submitted hash + loop { + let transaction_information = wait_until(&client.receiver, is_sidechain_block); + if let Some((hash, _)) = transaction_information { + if hash == submitted.unwrap().0 { + break transaction_information + } + } + } + } else { + None + }; + if let (Some(s), Some(c)) = (submitted, confirmed) { + // Assert the two hashes are identical + assert_eq!(s.0, c.0); + } + + BenchmarkTransaction { + started, + submitted: submitted.unwrap().1, + confirmed: confirmed.map(|v| v.1), + } +} + +fn is_submitted(s: TrustedOperationStatus) -> bool { + matches!(s, Submitted) +} + +fn is_sidechain_block(s: TrustedOperationStatus) -> bool { + matches!(s, InSidechainBlock(_)) +} diff --git a/tee-worker/cli/src/command_utils.rs b/tee-worker/cli/src/command_utils.rs new file mode 100644 index 0000000000..8b8b43c99d --- /dev/null +++ b/tee-worker/cli/src/command_utils.rs @@ -0,0 +1,87 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +use crate::Cli; +use base58::FromBase58; +use itc_rpc_client::direct_client::{DirectApi, DirectClient as DirectWorkerApi}; +use itp_node_api::api_client::{ParentchainApi, WsRpcClient}; +use log::*; +use my_node_runtime::{AccountId, Signature}; +use sgx_crypto_helper::rsa3072::Rsa3072PubKey; +use sp_application_crypto::sr25519; +use sp_core::{crypto::Ss58Codec, Pair}; +use sp_runtime::traits::{IdentifyAccount, Verify}; +use std::path::PathBuf; +use substrate_client_keystore::LocalKeystore; + +type AccountPublic = ::Signer; +pub(crate) const KEYSTORE_PATH: &str = "my_keystore"; + +/// Retrieves the public shielding key via the enclave websocket server. +pub(crate) fn get_shielding_key(cli: &Cli) -> Result { + let worker_api_direct = get_worker_api_direct(cli); + worker_api_direct.get_rsa_pubkey().map_err(|e| e.to_string()) +} + +pub(crate) fn get_chain_api(cli: &Cli) -> ParentchainApi { + let url = format!("{}:{}", cli.node_url, cli.node_port); + info!("connecting to {}", url); + ParentchainApi::new(WsRpcClient::new(&url)).unwrap() +} + +pub(crate) fn get_accountid_from_str(account: &str) -> AccountId { + match &account[..2] { + "//" => AccountPublic::from(sr25519::Pair::from_string(account, None).unwrap().public()) + .into_account(), + _ => AccountPublic::from(sr25519::Public::from_ss58check(account).unwrap()).into_account(), + } +} + +pub(crate) fn get_worker_api_direct(cli: &Cli) -> DirectWorkerApi { + let url = format!("{}:{}", cli.worker_url, cli.trusted_worker_port); + info!("Connecting to integritee-service-direct-port on '{}'", url); + DirectWorkerApi::new(url) +} + +/// get a pair either form keyring (well known keys) or from the store +pub(crate) fn get_pair_from_str(account: &str) -> sr25519::AppPair { + info!("getting pair for {}", account); + match &account[..2] { + "//" => sr25519::AppPair::from_string(account, None).unwrap(), + _ => { + info!("fetching from keystore at {}", &KEYSTORE_PATH); + // open store without password protection + let store = LocalKeystore::open(PathBuf::from(&KEYSTORE_PATH), None) + .expect("store should exist"); + info!("store opened"); + let _pair = store + .key_pair::( + &sr25519::Public::from_ss58check(account).unwrap().into(), + ) + .unwrap() + .unwrap(); + drop(store); + _pair + }, + } +} + +pub(crate) fn mrenclave_from_base58(src: &str) -> [u8; 32] { + let mut mrenclave = [0u8; 32]; + mrenclave.copy_from_slice(&src.from_base58().expect("mrenclave has to be base58 encoded")); + mrenclave +} diff --git a/tee-worker/cli/src/commands.rs b/tee-worker/cli/src/commands.rs new file mode 100644 index 0000000000..4eb3dac2e4 --- /dev/null +++ b/tee-worker/cli/src/commands.rs @@ -0,0 +1,47 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +extern crate chrono; +use crate::{base_cli::BaseCli, trusted_commands::TrustedArgs, Cli}; +use clap::Subcommand; + +#[cfg(feature = "teeracle")] +use crate::exchange_oracle::ExchangeOracleSubCommand; + +#[derive(Subcommand)] +pub enum Commands { + #[clap(flatten)] + Base(BaseCli), + + /// trusted calls to worker enclave + #[clap(after_help = "stf subcommands depend on the stf crate this has been built against")] + Trusted(TrustedArgs), + + /// Subcommands for the exchange oracle. + #[cfg(feature = "teeracle")] + #[clap(subcommand)] + ExchangeOracle(ExchangeOracleSubCommand), +} + +pub fn match_command(cli: &Cli) { + match &cli.command { + Commands::Base(cmd) => cmd.run(cli), + Commands::Trusted(cmd) => cmd.run(cli), + #[cfg(feature = "teeracle")] + Commands::ExchangeOracle(cmd) => cmd.run(cli), + }; +} diff --git a/tee-worker/cli/src/evm/commands/evm_call.rs b/tee-worker/cli/src/evm/commands/evm_call.rs new file mode 100644 index 0000000000..4bd7af62cc --- /dev/null +++ b/tee-worker/cli/src/evm/commands/evm_call.rs @@ -0,0 +1,85 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +use crate::{ + get_layer_two_evm_nonce, get_layer_two_nonce, + trusted_command_utils::{get_identifiers, get_pair_from_str}, + trusted_commands::TrustedArgs, + trusted_operation::perform_trusted_operation, + Cli, +}; +use codec::Decode; +use ita_stf::{Index, KeyPair, TrustedCall, TrustedGetter, TrustedOperation}; +use itp_types::AccountId; +use log::*; +use sp_core::{crypto::Ss58Codec, Pair, H160, U256}; +use std::{boxed::Box, vec::Vec}; +use substrate_api_client::utils::FromHexString; + +#[derive(Parser)] +pub struct EvmCallCommands { + /// Sender's incognito AccountId in ss58check format + from: String, + + /// Execution address of the smart contract + execution_address: String, + + /// Function hash + function: String, +} + +impl EvmCallCommands { + pub(crate) fn run(&self, cli: &Cli, trusted_args: &TrustedArgs) { + let sender = get_pair_from_str(trusted_args, &self.from); + let sender_acc: AccountId = sender.public().into(); + + info!("senders ss58 is {}", sender.public().to_ss58check()); + + let mut sender_evm_acc_slice: [u8; 20] = [0; 20]; + sender_evm_acc_slice + .copy_from_slice((<[u8; 32]>::from(sender_acc.clone())).get(0..20).unwrap()); + let sender_evm_acc: H160 = sender_evm_acc_slice.into(); + + info!("senders evm account is {}", sender_evm_acc); + + let execution_address = + H160::from_slice(&Vec::from_hex(self.execution_address.to_string()).unwrap()); + + let function_hash = Vec::from_hex(self.function.to_string()).unwrap(); + + let (mrenclave, shard) = get_identifiers(trusted_args); + let nonce = get_layer_two_nonce!(sender, cli, trusted_args); + let evm_nonce = get_layer_two_evm_nonce!(sender, cli, trusted_args); + + println!("calling smart contract function"); + let function_call = TrustedCall::evm_call( + sender_acc, + sender_evm_acc, + execution_address, + function_hash, + U256::from(0), + 10_000_000, // gas limit + U256::from(1), // max_fee_per_gas !>= min_gas_price defined in runtime + None, + Some(U256::from(evm_nonce)), + Vec::new(), + ) + .sign(&KeyPair::Sr25519(Box::new(sender)), nonce, &mrenclave, &shard) + .into_trusted_operation(trusted_args.direct); + let _ = perform_trusted_operation(cli, trusted_args, &function_call); + } +} diff --git a/tee-worker/cli/src/evm/commands/evm_command_utils.rs b/tee-worker/cli/src/evm/commands/evm_command_utils.rs new file mode 100644 index 0000000000..fe46dcacde --- /dev/null +++ b/tee-worker/cli/src/evm/commands/evm_command_utils.rs @@ -0,0 +1,36 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ +#[macro_export] +macro_rules! get_layer_two_evm_nonce { + ($signer_pair:ident, $cli:ident, $trusted_args:ident ) => {{ + let top: TrustedOperation = TrustedGetter::evm_nonce($signer_pair.public().into()) + .sign(&KeyPair::Sr25519(Box::new($signer_pair.clone()))) + .into(); + let res = perform_trusted_operation($cli, $trusted_args, &top); + let nonce: Index = if let Some(n) = res { + if let Ok(nonce) = Index::decode(&mut n.as_slice()) { + nonce + } else { + 0 + } + } else { + 0 + }; + debug!("got evm nonce: {:?}", nonce); + nonce + }}; +} diff --git a/tee-worker/cli/src/evm/commands/evm_create.rs b/tee-worker/cli/src/evm/commands/evm_create.rs new file mode 100644 index 0000000000..31e1c0d818 --- /dev/null +++ b/tee-worker/cli/src/evm/commands/evm_create.rs @@ -0,0 +1,89 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +use crate::{ + get_layer_two_evm_nonce, get_layer_two_nonce, + trusted_command_utils::{get_identifiers, get_pair_from_str}, + trusted_commands::TrustedArgs, + trusted_operation::perform_trusted_operation, + Cli, +}; +use codec::Decode; +use ita_stf::{ + evm_helpers::evm_create_address, Index, KeyPair, TrustedCall, TrustedGetter, TrustedOperation, +}; +use itp_types::AccountId; +use log::*; +use pallet_evm::{AddressMapping, HashedAddressMapping}; +use sp_core::{crypto::Ss58Codec, Pair, H160, U256}; +use sp_runtime::traits::BlakeTwo256; +use std::vec::Vec; +use substrate_api_client::utils::FromHexString; + +#[derive(Parser)] +pub struct EvmCreateCommands { + /// Sender's incognito AccountId in ss58check format + from: String, + + /// Smart Contract in Hex format + smart_contract: String, +} + +impl EvmCreateCommands { + pub(crate) fn run(&self, cli: &Cli, trusted_args: &TrustedArgs) { + let from = get_pair_from_str(trusted_args, &self.from); + let from_acc: AccountId = from.public().into(); + println!("from ss58 is {}", from.public().to_ss58check()); + + let mut sender_evm_acc_slice: [u8; 20] = [0; 20]; + sender_evm_acc_slice + .copy_from_slice((<[u8; 32]>::from(from_acc.clone())).get(0..20).unwrap()); + let sender_evm_acc: H160 = sender_evm_acc_slice.into(); + + let (mrenclave, shard) = get_identifiers(trusted_args); + + let sender_evm_substrate_addr = + HashedAddressMapping::::into_account_id(sender_evm_acc); + println!( + "Trying to get nonce of evm account {:?}", + sender_evm_substrate_addr.to_ss58check() + ); + + let nonce = get_layer_two_nonce!(from, cli, trusted_args); + let evm_account_nonce = get_layer_two_evm_nonce!(from, cli, trusted_args); + + let top = TrustedCall::evm_create( + from_acc, + sender_evm_acc, + Vec::from_hex(self.smart_contract.to_string()).unwrap(), + U256::from(0), + 967295, // gas limit + U256::from(1), // max_fee_per_gas !>= min_gas_price defined in runtime + None, + None, + Vec::new(), + ) + .sign(&from.into(), nonce, &mrenclave, &shard) + .into_trusted_operation(trusted_args.direct); + + let _ = perform_trusted_operation(cli, trusted_args, &top); + + let execution_address = evm_create_address(sender_evm_acc, evm_account_nonce); + info!("trusted call evm_create executed"); + println!("Created the smart contract with address {:?}", execution_address); + } +} diff --git a/tee-worker/cli/src/evm/commands/evm_read.rs b/tee-worker/cli/src/evm/commands/evm_read.rs new file mode 100644 index 0000000000..de44c1272c --- /dev/null +++ b/tee-worker/cli/src/evm/commands/evm_read.rs @@ -0,0 +1,77 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +use crate::{ + trusted_command_utils::get_pair_from_str, trusted_commands::TrustedArgs, + trusted_operation::perform_trusted_operation, Cli, +}; +use codec::Decode; +use ita_stf::{KeyPair, TrustedGetter, TrustedOperation}; +use itp_types::AccountId; +use log::*; +use sp_core::{crypto::Ss58Codec, Pair, H160, H256}; +use std::{boxed::Box, vec::Vec}; +use substrate_api_client::utils::FromHexString; + +#[derive(Parser)] +pub struct EvmReadCommands { + /// Sender's incognito AccountId in ss58check format + from: String, + + /// Execution address of the smart contract + execution_address: String, +} + +impl EvmReadCommands { + pub(crate) fn run(&self, cli: &Cli, trusted_args: &TrustedArgs) { + let sender = get_pair_from_str(trusted_args, &self.from); + let sender_acc: AccountId = sender.public().into(); + + info!("senders ss58 is {}", sender.public().to_ss58check()); + + let mut sender_evm_acc_slice: [u8; 20] = [0; 20]; + sender_evm_acc_slice + .copy_from_slice((<[u8; 32]>::from(sender_acc.clone())).get(0..20).unwrap()); + let sender_evm_acc: H160 = sender_evm_acc_slice.into(); + + info!("senders evm account is {}", sender_evm_acc); + + let execution_address = + H160::from_slice(&Vec::from_hex(self.execution_address.to_string()).unwrap()); + + let top: TrustedOperation = + TrustedGetter::evm_account_storages(sender_acc, execution_address, H256::zero()) + .sign(&KeyPair::Sr25519(Box::new(sender))) + .into(); + let res = perform_trusted_operation(cli, trusted_args, &top); + + debug!("received result for balance"); + let val = if let Some(v) = res { + if let Ok(vd) = H256::decode(&mut v.as_slice()) { + vd + } else { + error!("could not decode value. {:x?}", v); + H256::zero() + } + } else { + error!("Nothing in state!"); + H256::zero() + }; + + println!("{:?}", val); + } +} diff --git a/tee-worker/cli/src/evm/commands/mod.rs b/tee-worker/cli/src/evm/commands/mod.rs new file mode 100644 index 0000000000..014b093832 --- /dev/null +++ b/tee-worker/cli/src/evm/commands/mod.rs @@ -0,0 +1,6 @@ +pub mod evm_call; +pub mod evm_command_utils; +pub mod evm_create; +pub mod evm_read; + +pub use crate::get_layer_two_evm_nonce; diff --git a/tee-worker/cli/src/evm/mod.rs b/tee-worker/cli/src/evm/mod.rs new file mode 100644 index 0000000000..a89b5fd30d --- /dev/null +++ b/tee-worker/cli/src/evm/mod.rs @@ -0,0 +1,49 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +use crate::{ + evm::commands::{ + evm_call::EvmCallCommands, evm_create::EvmCreateCommands, evm_read::EvmReadCommands, + }, + trusted_commands::TrustedArgs, + Cli, +}; + +mod commands; + +#[allow(clippy::enum_variant_names)] +#[derive(Subcommand)] +pub enum EvmCommands { + /// Create smart contract + EvmCreate(EvmCreateCommands), + + /// Read smart contract storage + EvmRead(EvmReadCommands), + + /// Create smart contract + EvmCall(EvmCallCommands), +} + +impl EvmCommands { + pub fn run(&self, cli: &Cli, trusted_args: &TrustedArgs) { + match self { + EvmCommands::EvmCreate(cmd) => cmd.run(cli, trusted_args), + EvmCommands::EvmRead(cmd) => cmd.run(cli, trusted_args), + EvmCommands::EvmCall(cmd) => cmd.run(cli, trusted_args), + } + } +} diff --git a/tee-worker/cli/src/exchange_oracle/commands/add_to_whitelist.rs b/tee-worker/cli/src/exchange_oracle/commands/add_to_whitelist.rs new file mode 100644 index 0000000000..38eaf05eb4 --- /dev/null +++ b/tee-worker/cli/src/exchange_oracle/commands/add_to_whitelist.rs @@ -0,0 +1,59 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +use crate::{ + command_utils::{get_chain_api, get_pair_from_str, mrenclave_from_base58}, + Cli, +}; +use itp_node_api::api_client::{ADD_TO_WHITELIST, TEERACLE}; +use substrate_api_client::{compose_call, compose_extrinsic, UncheckedExtrinsicV4, XtStatus}; + +/// Add a trusted market data source to the on-chain whitelist. +#[derive(Debug, Clone, Parser)] +pub struct AddToWhitelistCmd { + /// Sender's on-chain AccountId in ss58check format. + /// + /// It has to be a sudo account. + from: String, + + /// Market data URL + source: String, + + /// MRENCLAVE of the oracle worker base58 encoded. + mrenclave: String, +} + +impl AddToWhitelistCmd { + pub fn run(&self, cli: &Cli) { + let api = get_chain_api(cli); + let mrenclave = mrenclave_from_base58(&self.mrenclave); + let from = get_pair_from_str(&self.from); + + let market_data_source = self.source.clone(); + + let api = api.set_signer(from.into()); + + let call = + compose_call!(api.metadata, TEERACLE, ADD_TO_WHITELIST, market_data_source, mrenclave); + + // compose the extrinsic + let xt: UncheckedExtrinsicV4<_, _> = compose_extrinsic!(api, "Sudo", "sudo", call); + + let tx_hash = api.send_extrinsic(xt.hex_encode(), XtStatus::Finalized).unwrap(); + println!("[+] Add to whitelist got finalized. Hash: {:?}\n", tx_hash); + } +} diff --git a/tee-worker/cli/src/exchange_oracle/commands/listen_to_exchange.rs b/tee-worker/cli/src/exchange_oracle/commands/listen_to_exchange.rs new file mode 100644 index 0000000000..60fce1ef9f --- /dev/null +++ b/tee-worker/cli/src/exchange_oracle/commands/listen_to_exchange.rs @@ -0,0 +1,85 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +use crate::{command_utils::get_chain_api, Cli}; +use codec::Decode; +use itp_node_api::api_client::ParentchainApi; +use itp_time_utils::{duration_now, remaining_time}; +use log::{debug, info}; +use my_node_runtime::{Event, Hash}; +use std::{sync::mpsc::channel, time::Duration}; +use substrate_api_client::FromHexString; + +/// Listen to exchange rate events. +#[derive(Debug, Clone, Parser)] +pub struct ListenToExchangeRateEventsCmd { + /// Listen for `duration` in seconds. + duration: u64, +} + +impl ListenToExchangeRateEventsCmd { + pub fn run(&self, cli: &Cli) { + let api = get_chain_api(cli); + let duration = Duration::from_secs(self.duration); + + let count = count_exchange_rate_update_events(&api, duration); + + println!("Number of ExchangeRateUpdated events received : "); + println!(" EVENTS_COUNT: {}", count); + } +} + +pub fn count_exchange_rate_update_events(api: &ParentchainApi, duration: Duration) -> u32 { + let stop = duration_now() + duration; + + //subscribe to events + let (events_in, events_out) = channel(); + api.subscribe_events(events_in).unwrap(); + let mut count = 0; + + while remaining_time(stop).unwrap_or_default() > Duration::ZERO { + let event_str = events_out.recv().unwrap(); + let unhex = Vec::from_hex(event_str).unwrap(); + let mut event_records_encoded = unhex.as_slice(); + let events_result = + Vec::>::decode(&mut event_records_encoded); + if let Ok(events) = events_result { + for event_record in &events { + info!("received event {:?}", event_record.event); + if let Event::Teeracle(event) = &event_record.event { + match &event { + my_node_runtime::pallet_teeracle::Event::ExchangeRateUpdated( + src, + trading_pair, + exchange_rate, + ) => { + count += 1; + debug!("Received ExchangeRateUpdated event"); + println!( + "ExchangeRateUpdated: TRADING_PAIR : {:?}, SRC : {:?}, VALUE :{:?}", + trading_pair, src, exchange_rate + ); + }, + _ => debug!("ignoring teeracle event: {:?}", event), + } + } + } + } + } + debug!("Received {} ExchangeRateUpdated event(s) in total", count); + count +} diff --git a/tee-worker/cli/src/exchange_oracle/commands/mod.rs b/tee-worker/cli/src/exchange_oracle/commands/mod.rs new file mode 100644 index 0000000000..4e4affb659 --- /dev/null +++ b/tee-worker/cli/src/exchange_oracle/commands/mod.rs @@ -0,0 +1,23 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +mod add_to_whitelist; +mod listen_to_exchange; + +pub use self::{ + add_to_whitelist::AddToWhitelistCmd, listen_to_exchange::ListenToExchangeRateEventsCmd, +}; diff --git a/tee-worker/cli/src/exchange_oracle/mod.rs b/tee-worker/cli/src/exchange_oracle/mod.rs new file mode 100644 index 0000000000..045988ca26 --- /dev/null +++ b/tee-worker/cli/src/exchange_oracle/mod.rs @@ -0,0 +1,45 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +//! Add cli commands for the exchange-rate oracle +//! +//! Todo: This shall be a standalone crate in app-libs/exchange-oracle. However, this needs: +//! https://github.com/integritee-network/worker/issues/852 + +use crate::Cli; +use commands::{AddToWhitelistCmd, ListenToExchangeRateEventsCmd}; + +mod commands; + +/// Exchange oracle subcommands for the cli. +#[derive(Debug, clap::Subcommand)] +pub enum ExchangeOracleSubCommand { + /// Add a market source to the teeracle's whitelist. + AddToWhitelist(AddToWhitelistCmd), + + /// Listen to exchange rate events + ListenToExchangeRateEvents(ListenToExchangeRateEventsCmd), +} + +impl ExchangeOracleSubCommand { + pub fn run(&self, cli: &Cli) { + match self { + ExchangeOracleSubCommand::AddToWhitelist(cmd) => cmd.run(cli), + ExchangeOracleSubCommand::ListenToExchangeRateEvents(cmd) => cmd.run(cli), + } + } +} diff --git a/tee-worker/cli/src/main.rs b/tee-worker/cli/src/main.rs new file mode 100644 index 0000000000..7694dec015 --- /dev/null +++ b/tee-worker/cli/src/main.rs @@ -0,0 +1,81 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +//! an RPC client to Integritee using websockets +//! +//! examples +//! integritee_cli 127.0.0.1:9944 transfer //Alice 5G9RtsTbiYJYQYMHbWfyPoeuuxNaCbC16tZ2JGrZ4gRKwz14 1000 +//! +#![feature(rustc_private)] +#[macro_use] +extern crate clap; +extern crate chrono; +extern crate env_logger; +extern crate log; + +mod base_cli; +mod benchmark; +mod command_utils; +mod commands; +#[cfg(feature = "evm")] +mod evm; +#[cfg(feature = "teeracle")] +mod exchange_oracle; +mod trusted_base_cli; +mod trusted_command_utils; +mod trusted_commands; +mod trusted_operation; + +use crate::commands::Commands; +use clap::Parser; + +const VERSION: &str = env!("CARGO_PKG_VERSION"); + +#[derive(Parser)] +#[clap(name = "integritee-cli")] +#[clap(version = VERSION)] +#[clap(author = "Integritee AG ")] +#[clap(about = "interact with integritee-node and workers", long_about = None)] +#[clap(after_help = "stf subcommands depend on the stf crate this has been built against")] +pub struct Cli { + /// node url + #[clap(short = 'u', long, default_value_t = String::from("ws://127.0.0.1"))] + node_url: String, + + /// node port + #[clap(short = 'p', long, default_value_t = String::from("9944"))] + node_port: String, + + /// worker url + #[clap(short = 'U', long, default_value_t = String::from("wss://127.0.0.1"))] + worker_url: String, + + /// worker direct invocation port + #[clap(short = 'P', long, default_value_t = String::from("2000"))] + trusted_worker_port: String, + + #[clap(subcommand)] + command: Commands, +} + +fn main() { + env_logger::init(); + + let cli = Cli::parse(); + + commands::match_command(&cli); +} diff --git a/tee-worker/cli/src/trusted_base_cli/commands/balance.rs b/tee-worker/cli/src/trusted_base_cli/commands/balance.rs new file mode 100644 index 0000000000..a54cd2c3d7 --- /dev/null +++ b/tee-worker/cli/src/trusted_base_cli/commands/balance.rs @@ -0,0 +1,30 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +use crate::{trusted_command_utils::get_balance, trusted_commands::TrustedArgs, Cli}; + +#[derive(Parser)] +pub struct BalanceCommand { + /// AccountId in ss58check format + account: String, +} + +impl BalanceCommand { + pub(crate) fn run(&self, cli: &Cli, trusted_args: &TrustedArgs) { + println!("{}", get_balance(cli, trusted_args, &self.account).unwrap_or_default()); + } +} diff --git a/tee-worker/cli/src/trusted_base_cli/commands/litentry/mod.rs b/tee-worker/cli/src/trusted_base_cli/commands/litentry/mod.rs new file mode 100644 index 0000000000..901df09144 --- /dev/null +++ b/tee-worker/cli/src/trusted_base_cli/commands/litentry/mod.rs @@ -0,0 +1,20 @@ +// Copyright 2020-2022 Litentry Technologies GmbH. +// This file is part of Litentry. +// +// Litentry is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Litentry is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Litentry. If not, see . + +pub mod set_challenge_code; +pub mod set_user_shielding_preflight; +pub mod user_shielding_key; +pub mod verify_identity_preflight; diff --git a/tee-worker/cli/src/trusted_base_cli/commands/litentry/set_challenge_code.rs b/tee-worker/cli/src/trusted_base_cli/commands/litentry/set_challenge_code.rs new file mode 100644 index 0000000000..a0aeeaf8d4 --- /dev/null +++ b/tee-worker/cli/src/trusted_base_cli/commands/litentry/set_challenge_code.rs @@ -0,0 +1,57 @@ +// Copyright 2020-2022 Litentry Technologies GmbH. +// This file is part of Litentry. +// +// Litentry is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Litentry is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Litentry. If not, see . + +use crate::{ + get_layer_two_nonce, + trusted_command_utils::{get_accountid_from_str, get_identifiers, get_pair_from_str}, + trusted_commands::TrustedArgs, + trusted_operation::perform_trusted_operation, + Cli, +}; +use codec::Decode; +use ita_stf::{Index, KeyPair, TrustedCall, TrustedGetter, TrustedOperation}; +use log::*; +use sp_core::Pair; + +#[derive(Parser)] +pub struct SetChallengeCodeCommand { + /// AccountId in ss58check format + account: String, + identity: String, + /// challenge code in hex string + code_hex: String, +} + +impl SetChallengeCodeCommand { + pub(crate) fn run(&self, cli: &Cli, trusted_args: &TrustedArgs) { + let who = get_accountid_from_str(&self.account); + let root = get_pair_from_str(trusted_args, "//Alice"); + + let (mrenclave, shard) = get_identifiers(trusted_args); + let nonce = get_layer_two_nonce!(root, cli, trusted_args); + // compose the extrinsic + let identity = serde_json::from_str(self.identity.as_str()).unwrap(); + + let mut code = [0u8; 16]; + hex::decode_to_slice(&self.code_hex, &mut code).expect("decoding code failed"); + + let top: TrustedOperation = + TrustedCall::set_challenge_code_runtime(root.public().into(), who, identity, code) + .sign(&KeyPair::Sr25519(Box::new(root)), nonce, &mrenclave, &shard) + .into_trusted_operation(trusted_args.direct); + perform_trusted_operation(cli, trusted_args, &top); + } +} diff --git a/tee-worker/cli/src/trusted_base_cli/commands/litentry/set_user_shielding_preflight.rs b/tee-worker/cli/src/trusted_base_cli/commands/litentry/set_user_shielding_preflight.rs new file mode 100644 index 0000000000..d8398456f5 --- /dev/null +++ b/tee-worker/cli/src/trusted_base_cli/commands/litentry/set_user_shielding_preflight.rs @@ -0,0 +1,56 @@ +// Copyright 2020-2022 Litentry Technologies GmbH. +// This file is part of Litentry. +// +// Litentry is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Litentry is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Litentry. If not, see . + +use crate::{ + get_layer_two_nonce, + trusted_command_utils::{get_accountid_from_str, get_identifiers, get_pair_from_str}, + trusted_commands::TrustedArgs, + trusted_operation::perform_trusted_operation, + Cli, +}; +use codec::Decode; +use ita_stf::{Index, KeyPair, TrustedCall, TrustedGetter, TrustedOperation}; +use litentry_primitives::UserShieldingKeyType; +use log::*; +use sp_core::Pair; + +#[derive(Parser)] +pub struct SetUserShieldingKeyPreflightCommand { + /// AccountId in ss58check format + account: String, + /// Shielding key in hex string + key_hex: String, +} + +impl SetUserShieldingKeyPreflightCommand { + pub(crate) fn run(&self, cli: &Cli, trusted_args: &TrustedArgs) { + let who = get_accountid_from_str(&self.account); + let root = get_pair_from_str(trusted_args, "//Alice"); + + let (mrenclave, shard) = get_identifiers(trusted_args); + let nonce = get_layer_two_nonce!(root, cli, trusted_args); + + let mut key = UserShieldingKeyType::default(); + + hex::decode_to_slice(&self.key_hex, &mut key).expect("decoding shielding_key failed"); + + let top: TrustedOperation = + TrustedCall::set_user_shielding_key_preflight(root.public().into(), who, key) + .sign(&KeyPair::Sr25519(Box::new(root)), nonce, &mrenclave, &shard) + .into_trusted_operation(trusted_args.direct); + perform_trusted_operation(cli, trusted_args, &top); + } +} diff --git a/tee-worker/cli/src/trusted_base_cli/commands/litentry/user_shielding_key.rs b/tee-worker/cli/src/trusted_base_cli/commands/litentry/user_shielding_key.rs new file mode 100644 index 0000000000..9522c7238a --- /dev/null +++ b/tee-worker/cli/src/trusted_base_cli/commands/litentry/user_shielding_key.rs @@ -0,0 +1,42 @@ +// Copyright 2020-2022 Litentry Technologies GmbH. +// This file is part of Litentry. +// +// Litentry is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Litentry is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Litentry. If not, see . + +use crate::{ + trusted_command_utils::get_pair_from_str, trusted_commands::TrustedArgs, + trusted_operation::perform_trusted_operation, Cli, +}; +use codec::Decode; +use ita_stf::{KeyPair, TrustedGetter, TrustedOperation}; +use litentry_primitives::UserShieldingKeyType; +use sp_core::Pair; + +#[derive(Parser)] +pub struct UserShiledingKeyCommand { + /// AccountId in ss58check format + account: String, +} + +impl UserShiledingKeyCommand { + pub(crate) fn run(&self, cli: &Cli, trusted_args: &TrustedArgs) { + let who = get_pair_from_str(trusted_args, &self.account); + let top: TrustedOperation = TrustedGetter::user_shielding_key(who.public().into()) + .sign(&KeyPair::Sr25519(Box::new(who))) + .into(); + let key = perform_trusted_operation(cli, trusted_args, &top) + .and_then(|v| UserShieldingKeyType::decode(&mut v.as_slice()).ok()); + println!("{}", hex::encode(key.unwrap())); + } +} diff --git a/tee-worker/cli/src/trusted_base_cli/commands/litentry/verify_identity_preflight.rs b/tee-worker/cli/src/trusted_base_cli/commands/litentry/verify_identity_preflight.rs new file mode 100644 index 0000000000..901f1f70ba --- /dev/null +++ b/tee-worker/cli/src/trusted_base_cli/commands/litentry/verify_identity_preflight.rs @@ -0,0 +1,69 @@ +// Copyright 2020-2022 Litentry Technologies GmbH. +// This file is part of Litentry. +// +// Litentry is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Litentry is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Litentry. If not, see . + +use crate::{ + get_layer_two_nonce, + trusted_command_utils::{get_accountid_from_str, get_identifiers, get_pair_from_str}, + trusted_commands::TrustedArgs, + trusted_operation::perform_trusted_operation, + Cli, +}; +use codec::Decode; +use ita_stf::{Index, KeyPair, TrustedCall, TrustedGetter, TrustedOperation}; +use log::*; +use sp_core::Pair; + +#[derive(Parser)] +pub struct VerifyIdentityPreflightCommand { + /// AccountId in ss58check format + account: String, + did: String, + validation_data: String, + parent_block_number: u32, +} + +// TODO: we'd need an "integration-test" with parentchain "verify_identity" +// the origin of it needs to be re-considered if we want individual steps +impl VerifyIdentityPreflightCommand { + pub(crate) fn run(&self, cli: &Cli, trusted_args: &TrustedArgs) { + let who = get_accountid_from_str(&self.account); + let root = get_pair_from_str(trusted_args, "//Alice"); + + let (mrenclave, shard) = get_identifiers(trusted_args); + let nonce = get_layer_two_nonce!(root, cli, trusted_args); + // compose the extrinsic + let validation_data = serde_json::from_str(self.validation_data.as_str()); + if let Err(e) = validation_data { + warn!("Deserialize ValidationData error: {:?}", e.to_string()); + return + } + let identity = serde_json::from_str(self.did.as_str()); + if let Err(e) = identity { + warn!("Deserialize Identity error: {:?}", e.to_string()); + return + } + let top: TrustedOperation = TrustedCall::verify_identity_preflight( + root.public().into(), + who, + identity.unwrap(), + validation_data.unwrap(), + self.parent_block_number, + ) + .sign(&KeyPair::Sr25519(Box::new(root)), nonce, &mrenclave, &shard) + .into_trusted_operation(trusted_args.direct); + let _ = perform_trusted_operation(cli, trusted_args, &top); + } +} diff --git a/tee-worker/cli/src/trusted_base_cli/commands/mod.rs b/tee-worker/cli/src/trusted_base_cli/commands/mod.rs new file mode 100644 index 0000000000..f1709fd705 --- /dev/null +++ b/tee-worker/cli/src/trusted_base_cli/commands/mod.rs @@ -0,0 +1,5 @@ +pub mod balance; +pub mod litentry; +pub mod set_balance; +pub mod transfer; +pub mod unshield_funds; diff --git a/tee-worker/cli/src/trusted_base_cli/commands/set_balance.rs b/tee-worker/cli/src/trusted_base_cli/commands/set_balance.rs new file mode 100644 index 0000000000..bed22cd785 --- /dev/null +++ b/tee-worker/cli/src/trusted_base_cli/commands/set_balance.rs @@ -0,0 +1,61 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +use crate::{ + get_layer_two_nonce, + trusted_command_utils::{get_identifiers, get_pair_from_str}, + trusted_commands::TrustedArgs, + trusted_operation::perform_trusted_operation, + Cli, +}; +use codec::Decode; +use ita_stf::{Index, KeyPair, TrustedCall, TrustedGetter, TrustedOperation}; +use log::*; +use my_node_runtime::Balance; +use sp_core::{crypto::Ss58Codec, Pair}; +use std::boxed::Box; + +#[derive(Parser)] +pub struct SetBalanceCommand { + /// sender's AccountId in ss58check format + account: String, + + /// amount to be transferred + amount: Balance, +} + +impl SetBalanceCommand { + pub(crate) fn run(&self, cli: &Cli, trusted_args: &TrustedArgs) { + let who = get_pair_from_str(trusted_args, &self.account); + let signer = get_pair_from_str(trusted_args, "//Alice"); + info!("account ss58 is {}", who.public().to_ss58check()); + + println!("send trusted call set-balance({}, {})", who.public(), self.amount); + + let (mrenclave, shard) = get_identifiers(trusted_args); + let nonce = get_layer_two_nonce!(signer, cli, trusted_args); + let top: TrustedOperation = TrustedCall::balance_set_balance( + signer.public().into(), + who.public().into(), + self.amount, + self.amount, + ) + .sign(&KeyPair::Sr25519(Box::new(signer)), nonce, &mrenclave, &shard) + .into_trusted_operation(trusted_args.direct); + let _ = perform_trusted_operation(cli, trusted_args, &top); + } +} diff --git a/tee-worker/cli/src/trusted_base_cli/commands/transfer.rs b/tee-worker/cli/src/trusted_base_cli/commands/transfer.rs new file mode 100644 index 0000000000..a6760b3618 --- /dev/null +++ b/tee-worker/cli/src/trusted_base_cli/commands/transfer.rs @@ -0,0 +1,61 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +use crate::{ + get_layer_two_nonce, + trusted_command_utils::{get_accountid_from_str, get_identifiers, get_pair_from_str}, + trusted_commands::TrustedArgs, + trusted_operation::perform_trusted_operation, + Cli, +}; +use codec::Decode; +use ita_stf::{Index, KeyPair, TrustedCall, TrustedGetter, TrustedOperation}; +use log::*; +use my_node_runtime::Balance; +use sp_core::{crypto::Ss58Codec, Pair}; +use std::boxed::Box; + +#[derive(Parser)] +pub struct TransferCommand { + /// sender's AccountId in ss58check format + from: String, + + /// recipient's AccountId in ss58check format + to: String, + + /// amount to be transferred + amount: Balance, +} + +impl TransferCommand { + pub(crate) fn run(&self, cli: &Cli, trusted_args: &TrustedArgs) { + let from = get_pair_from_str(trusted_args, &self.from); + let to = get_accountid_from_str(&self.to); + info!("from ss58 is {}", from.public().to_ss58check()); + info!("to ss58 is {}", to.to_ss58check()); + + println!("send trusted call transfer from {} to {}: {}", from.public(), to, self.amount); + let (mrenclave, shard) = get_identifiers(trusted_args); + let nonce = get_layer_two_nonce!(from, cli, trusted_args); + let top: TrustedOperation = + TrustedCall::balance_transfer(from.public().into(), to, self.amount) + .sign(&KeyPair::Sr25519(Box::new(from)), nonce, &mrenclave, &shard) + .into_trusted_operation(trusted_args.direct); + let _ = perform_trusted_operation(cli, trusted_args, &top); + info!("trusted call transfer executed"); + } +} diff --git a/tee-worker/cli/src/trusted_base_cli/commands/unshield_funds.rs b/tee-worker/cli/src/trusted_base_cli/commands/unshield_funds.rs new file mode 100644 index 0000000000..62958110e3 --- /dev/null +++ b/tee-worker/cli/src/trusted_base_cli/commands/unshield_funds.rs @@ -0,0 +1,66 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +use crate::{ + get_layer_two_nonce, + trusted_command_utils::{get_accountid_from_str, get_identifiers, get_pair_from_str}, + trusted_commands::TrustedArgs, + trusted_operation::perform_trusted_operation, + Cli, +}; +use codec::Decode; +use ita_stf::{Index, KeyPair, TrustedCall, TrustedGetter, TrustedOperation}; +use log::*; +use my_node_runtime::Balance; +use sp_core::{crypto::Ss58Codec, Pair}; +use std::boxed::Box; + +#[derive(Parser)] +pub struct UnshieldFundsCommand { + /// Sender's incognito AccountId in ss58check format + from: String, + + /// Recipient's parentchain AccountId in ss58check format + to: String, + + /// amount to be transferred + amount: Balance, +} + +impl UnshieldFundsCommand { + pub(crate) fn run(&self, cli: &Cli, trusted_args: &TrustedArgs) { + let from = get_pair_from_str(trusted_args, &self.from); + let to = get_accountid_from_str(&self.to); + println!("from ss58 is {}", from.public().to_ss58check()); + println!("to ss58 is {}", to.to_ss58check()); + + println!( + "send trusted call unshield_funds from {} to {}: {}", + from.public(), + to, + self.amount + ); + + let (mrenclave, shard) = get_identifiers(trusted_args); + let nonce = get_layer_two_nonce!(from, cli, trusted_args); + let top: TrustedOperation = + TrustedCall::balance_unshield(from.public().into(), to, self.amount, shard) + .sign(&KeyPair::Sr25519(Box::new(from)), nonce, &mrenclave, &shard) + .into_trusted_operation(trusted_args.direct); + let _ = perform_trusted_operation(cli, trusted_args, &top); + } +} diff --git a/tee-worker/cli/src/trusted_base_cli/mod.rs b/tee-worker/cli/src/trusted_base_cli/mod.rs new file mode 100644 index 0000000000..0686281c17 --- /dev/null +++ b/tee-worker/cli/src/trusted_base_cli/mod.rs @@ -0,0 +1,111 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +use crate::{ + trusted_base_cli::commands::{ + balance::BalanceCommand, + litentry::{ + set_challenge_code::SetChallengeCodeCommand, + set_user_shielding_preflight::SetUserShieldingKeyPreflightCommand, + user_shielding_key::UserShiledingKeyCommand, + verify_identity_preflight::VerifyIdentityPreflightCommand, + }, + set_balance::SetBalanceCommand, + transfer::TransferCommand, + unshield_funds::UnshieldFundsCommand, + }, + trusted_command_utils::get_keystore_path, + trusted_commands::TrustedArgs, + Cli, +}; +use log::*; +use sp_application_crypto::{ed25519, sr25519}; +use sp_core::{crypto::Ss58Codec, Pair}; +use substrate_client_keystore::{KeystoreExt, LocalKeystore}; + +mod commands; + +#[derive(Subcommand)] +pub enum TrustedBaseCli { + /// generates a new incognito account for the given shard + NewAccount, + + /// lists all incognito accounts in a given shard + ListAccounts, + + /// send funds from one incognito account to another + Transfer(TransferCommand), + + /// ROOT call to set some account balance to an arbitrary number + SetBalance(SetBalanceCommand), + + /// query balance for incognito account in keystore + Balance(BalanceCommand), + + /// Transfer funds from an incognito account to an parentchain account + UnshieldFunds(UnshieldFundsCommand), + + // Litentry's commands below + // for commands that should trigger parentchain extrins, check non-trusted commands + /// query a user's shielding key, the setter is non-trusted command + UserShieldingKey(UserShiledingKeyCommand), + + SetChallengeCode(SetChallengeCodeCommand), + + VerifyIdentityPreflight(VerifyIdentityPreflightCommand), + + SetUserShieldingKeyPreflight(SetUserShieldingKeyPreflightCommand), +} + +impl TrustedBaseCli { + pub fn run(&self, cli: &Cli, trusted_args: &TrustedArgs) { + match self { + TrustedBaseCli::NewAccount => new_account(trusted_args), + TrustedBaseCli::ListAccounts => list_accounts(trusted_args), + TrustedBaseCli::Transfer(cmd) => cmd.run(cli, trusted_args), + TrustedBaseCli::SetBalance(cmd) => cmd.run(cli, trusted_args), + TrustedBaseCli::Balance(cmd) => cmd.run(cli, trusted_args), + TrustedBaseCli::UnshieldFunds(cmd) => cmd.run(cli, trusted_args), + // Litentry's commands below + TrustedBaseCli::UserShieldingKey(cmd) => cmd.run(cli, trusted_args), + TrustedBaseCli::SetChallengeCode(cmd) => cmd.run(cli, trusted_args), + TrustedBaseCli::VerifyIdentityPreflight(cmd) => cmd.run(cli, trusted_args), + TrustedBaseCli::SetUserShieldingKeyPreflight(cmd) => cmd.run(cli, trusted_args), + } + } +} + +fn new_account(trusted_args: &TrustedArgs) { + let store = LocalKeystore::open(get_keystore_path(trusted_args), None).unwrap(); + let key: sr25519::AppPair = store.generate().unwrap(); + drop(store); + info!("new account {}", key.public().to_ss58check()); + println!("{}", key.public().to_ss58check()); +} + +fn list_accounts(trusted_args: &TrustedArgs) { + let store = LocalKeystore::open(get_keystore_path(trusted_args), None).unwrap(); + info!("sr25519 keys:"); + for pubkey in store.public_keys::().unwrap().into_iter() { + println!("{}", pubkey.to_ss58check()); + } + info!("ed25519 keys:"); + for pubkey in store.public_keys::().unwrap().into_iter() { + println!("{}", pubkey.to_ss58check()); + } + drop(store); +} diff --git a/tee-worker/cli/src/trusted_command_utils.rs b/tee-worker/cli/src/trusted_command_utils.rs new file mode 100644 index 0000000000..6d9de4d951 --- /dev/null +++ b/tee-worker/cli/src/trusted_command_utils.rs @@ -0,0 +1,128 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +use crate::{ + command_utils::mrenclave_from_base58, trusted_commands::TrustedArgs, + trusted_operation::perform_trusted_operation, Cli, +}; +use base58::{FromBase58, ToBase58}; +use codec::{Decode, Encode}; +use ita_stf::{AccountId, KeyPair, ShardIdentifier, TrustedGetter, TrustedOperation}; +use log::*; +use my_node_runtime::Balance; +use sp_application_crypto::sr25519; +use sp_core::{crypto::Ss58Codec, sr25519 as sr25519_core, Pair}; +use sp_runtime::traits::IdentifyAccount; +use std::{boxed::Box, path::PathBuf}; +use substrate_client_keystore::LocalKeystore; + +#[macro_export] +macro_rules! get_layer_two_nonce { + ($signer_pair:ident, $cli: ident, $trusted_args:ident ) => {{ + let top: TrustedOperation = TrustedGetter::nonce($signer_pair.public().into()) + .sign(&KeyPair::Sr25519(Box::new($signer_pair.clone()))) + .into(); + let res = perform_trusted_operation($cli, $trusted_args, &top); + let nonce: Index = if let Some(n) = res { + if let Ok(nonce) = Index::decode(&mut n.as_slice()) { + nonce + } else { + 0 + } + } else { + 0 + }; + debug!("got layer two nonce: {:?}", nonce); + nonce + }}; +} + +const TRUSTED_KEYSTORE_PATH: &str = "my_trusted_keystore"; + +pub(crate) fn get_balance(cli: &Cli, trusted_args: &TrustedArgs, arg_who: &str) -> Option { + debug!("arg_who = {:?}", arg_who); + let who = get_pair_from_str(trusted_args, arg_who); + let top: TrustedOperation = TrustedGetter::free_balance(who.public().into()) + .sign(&KeyPair::Sr25519(Box::new(who))) + .into(); + let res = perform_trusted_operation(cli, trusted_args, &top); + debug!("received result for balance"); + decode_balance(res) +} + +pub(crate) fn decode_balance(maybe_encoded_balance: Option>) -> Option { + maybe_encoded_balance.and_then(|encoded_balance| { + if let Ok(vd) = Balance::decode(&mut encoded_balance.as_slice()) { + Some(vd) + } else { + warn!("Could not decode balance. maybe hasn't been set? {:x?}", encoded_balance); + None + } + }) +} + +pub(crate) fn get_keystore_path(trusted_args: &TrustedArgs) -> PathBuf { + let (_mrenclave, shard) = get_identifiers(trusted_args); + PathBuf::from(&format!("{}/{}", TRUSTED_KEYSTORE_PATH, shard.encode().to_base58())) +} + +pub(crate) fn get_identifiers(trusted_args: &TrustedArgs) -> ([u8; 32], ShardIdentifier) { + let mrenclave = mrenclave_from_base58(&trusted_args.mrenclave); + let shard = match &trusted_args.shard { + Some(val) => + ShardIdentifier::from_slice(&val.from_base58().expect("shard has to be base58 encoded")), + None => ShardIdentifier::from_slice(&mrenclave), + }; + (mrenclave, shard) +} + +// TODO this function is redundant with client::main +pub(crate) fn get_accountid_from_str(account: &str) -> AccountId { + match &account[..2] { + "//" => sr25519::Pair::from_string(account, None) + .unwrap() + .public() + .into_account() + .into(), + _ => sr25519::Public::from_ss58check(account).unwrap().into_account().into(), + } +} + +// TODO this function is ALMOST redundant with client::main +// get a pair either form keyring (well known keys) or from the store +pub(crate) fn get_pair_from_str(trusted_args: &TrustedArgs, account: &str) -> sr25519_core::Pair { + info!("getting pair for {}", account); + match &account[..2] { + "//" => sr25519_core::Pair::from_string(account, None).unwrap(), + _ => { + info!("fetching from keystore at {}", &TRUSTED_KEYSTORE_PATH); + // open store without password protection + let store = LocalKeystore::open(get_keystore_path(trusted_args), None) + .expect("store should exist"); + info!("store opened"); + let _pair = store + .key_pair::( + &sr25519::Public::from_ss58check(account).unwrap().into(), + ) + .unwrap() + .unwrap(); + info!("key pair fetched"); + drop(store); + _pair.into() + }, + } +} diff --git a/tee-worker/cli/src/trusted_commands.rs b/tee-worker/cli/src/trusted_commands.rs new file mode 100644 index 0000000000..1012e9d697 --- /dev/null +++ b/tee-worker/cli/src/trusted_commands.rs @@ -0,0 +1,68 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +use crate::{benchmark::BenchmarkCommands, Cli}; + +#[cfg(feature = "evm")] +use crate::evm::EvmCommands; +use crate::trusted_base_cli::TrustedBaseCli; + +#[derive(Args)] +pub struct TrustedArgs { + /// targeted worker MRENCLAVE + #[clap(short, long)] + pub(crate) mrenclave: String, + + /// shard identifier + #[clap(short, long)] + pub(crate) shard: Option, + + /// signer for publicly observable extrinsic + #[clap(short='a', long, default_value_t = String::from("//Alice"))] + pub(crate) xt_signer: String, + + /// insert if direct invocation call is desired + #[clap(short, long)] + pub(crate) direct: bool, + + #[clap(subcommand)] + pub(crate) command: TrustedCommands, +} + +#[derive(Subcommand)] +pub enum TrustedCommands { + #[clap(flatten)] + BaseTrusted(TrustedBaseCli), + + #[cfg(feature = "evm")] + #[clap(flatten)] + EvmCommands(EvmCommands), + + /// Run Benchmark + Benchmark(BenchmarkCommands), +} + +impl TrustedArgs { + pub(crate) fn run(&self, cli: &Cli) { + match &self.command { + TrustedCommands::BaseTrusted(cmd) => cmd.run(cli, self), + TrustedCommands::Benchmark(benchmark_commands) => benchmark_commands.run(cli, self), + #[cfg(feature = "evm")] + TrustedCommands::EvmCommands(evm_commands) => evm_commands.run(cli, self), + } + } +} diff --git a/tee-worker/cli/src/trusted_operation.rs b/tee-worker/cli/src/trusted_operation.rs new file mode 100644 index 0000000000..032d0700b5 --- /dev/null +++ b/tee-worker/cli/src/trusted_operation.rs @@ -0,0 +1,351 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +use crate::{ + command_utils::{get_chain_api, get_pair_from_str, get_shielding_key, get_worker_api_direct}, + trusted_commands::TrustedArgs, + Cli, +}; +use base58::FromBase58; +use codec::{Decode, Encode}; +use ita_stf::{Getter, ShardIdentifier, TrustedOperation}; +use itc_rpc_client::direct_client::{DirectApi, DirectClient}; +use itp_node_api::api_client::TEEREX; +use itp_rpc::{RpcRequest, RpcResponse, RpcReturnValue}; +use itp_sgx_crypto::ShieldingCryptoEncrypt; +use itp_types::{BlockNumber, DirectRequestStatus, Header, TrustedOperationStatus}; +use itp_utils::{FromHexPrefixed, ToHexPrefixed}; +use log::*; +use my_node_runtime::{AccountId, Hash}; +use sp_core::{sr25519 as sr25519_core, H256}; +use std::{ + result::Result as StdResult, + sync::mpsc::{channel, Receiver}, + time::Instant, +}; +use substrate_api_client::{compose_extrinsic, XtStatus}; +use teerex_primitives::Request; + +pub(crate) fn perform_trusted_operation( + cli: &Cli, + trusted_args: &TrustedArgs, + top: &TrustedOperation, +) -> Option> { + match top { + TrustedOperation::indirect_call(_) => send_request(cli, trusted_args, top), + TrustedOperation::direct_call(_) => send_direct_request(cli, trusted_args, top), + TrustedOperation::get(getter) => execute_getter_from_cli_args(cli, trusted_args, getter), + } +} + +fn execute_getter_from_cli_args( + cli: &Cli, + trusted_args: &TrustedArgs, + getter: &Getter, +) -> Option> { + let shard = read_shard(trusted_args).unwrap(); + let direct_api = get_worker_api_direct(cli); + get_state(&direct_api, shard, getter) +} + +pub(crate) fn get_state( + direct_api: &DirectClient, + shard: ShardIdentifier, + getter: &Getter, +) -> Option> { + // Compose jsonrpc call. + let data = Request { shard, cyphertext: getter.encode() }; + let rpc_method = "state_executeGetter".to_owned(); + let jsonrpc_call: String = + RpcRequest::compose_jsonrpc_call(rpc_method, vec![data.to_hex()]).unwrap(); + + let rpc_response_str = direct_api.get(&jsonrpc_call).unwrap(); + + // Decode RPC response. + let rpc_response: RpcResponse = serde_json::from_str(&rpc_response_str).ok()?; + let rpc_return_value = RpcReturnValue::from_hex(&rpc_response.result) + // Replace with `inspect_err` once it's stable. + .map_err(|e| { + error!("Failed to decode RpcReturnValue: {:?}", e); + e + }) + .ok()?; + + if rpc_return_value.status == DirectRequestStatus::Error { + println!("[Error] {}", String::decode(&mut rpc_return_value.value.as_slice()).unwrap()); + return None + } + + let maybe_state = Option::decode(&mut rpc_return_value.value.as_slice()) + // Replace with `inspect_err` once it's stable. + .map_err(|e| { + error!("Failed to decode return value: {:?}", e); + e + }) + .ok()?; + + maybe_state +} + +fn send_request( + cli: &Cli, + trusted_args: &TrustedArgs, + trusted_operation: &TrustedOperation, +) -> Option> { + let chain_api = get_chain_api(cli); + let encryption_key = get_shielding_key(cli).unwrap(); + let call_encrypted = encryption_key.encrypt(&trusted_operation.encode()).unwrap(); + + let shard = read_shard(trusted_args).unwrap(); + + let arg_signer = &trusted_args.xt_signer; + let signer = get_pair_from_str(arg_signer); + let _chain_api = chain_api.set_signer(sr25519_core::Pair::from(signer)); + + let request = Request { shard, cyphertext: call_encrypted }; + let xt = compose_extrinsic!(_chain_api, TEEREX, "call_worker", request); + + // send and watch extrinsic until block is executed + let block_hash = + _chain_api.send_extrinsic(xt.hex_encode(), XtStatus::InBlock).unwrap().unwrap(); + + info!( + "Trusted call extrinsic sent and successfully included in parentchain block with hash {:?}.", + block_hash + ); + info!("Waiting for execution confirmation from enclave..."); + let (events_in, events_out) = channel(); + _chain_api.subscribe_events(events_in).unwrap(); + + loop { + let ret: ProcessedParentchainBlockArgs = _chain_api + .wait_for_event::( + TEEREX, + "ProcessedParentchainBlock", + None, + &events_out, + ) + .unwrap(); + info!("Confirmation of ProcessedParentchainBlock received"); + debug!("Expected block Hash: {:?}", block_hash); + debug!("Confirmed stf block Hash: {:?}", ret.block_hash); + match _chain_api.get_header::
(Some(block_hash)) { + Ok(option) => { + match option { + None => { + error!("Could not get Block Header"); + return None + }, + Some(header) => { + let block_number: BlockNumber = header.number; + info!("Expected block Number: {:?}", block_number); + info!("Confirmed block Number: {:?}", ret.block_number); + // The returned block number belongs to a subsequent event. We missed our event and can break the loop. + if ret.block_number > block_number { + warn!( + "Received block number ({:?}) exceeds expected one ({:?}) ", + ret.block_number, block_number + ); + return None + } + // The block number is correct, but the block hash does not fit. + if block_number == ret.block_number && block_hash != ret.block_hash { + error!( + "Block hash for event does not match expected hash. Expected: {:?}, returned: {:?}", + block_hash, ret.block_hash); + return None + } + }, + } + }, + Err(err) => { + error!("Could not get Block Header, due to error: {:?}", err); + return None + }, + } + if ret.block_hash == block_hash { + return Some(ret.block_hash.encode()) + } + } +} + +fn read_shard(trusted_args: &TrustedArgs) -> StdResult { + match &trusted_args.shard { + Some(s) => match s.from_base58() { + Ok(s) => ShardIdentifier::decode(&mut &s[..]), + _ => panic!("shard argument must be base58 encoded"), + }, + None => match trusted_args.mrenclave.from_base58() { + Ok(s) => ShardIdentifier::decode(&mut &s[..]), + _ => panic!("mrenclave argument must be base58 encoded"), + }, + } +} + +/// sends a rpc watch request to the worker api server +fn send_direct_request( + cli: &Cli, + trusted_args: &TrustedArgs, + operation_call: &TrustedOperation, +) -> Option> { + let encryption_key = get_shielding_key(cli).unwrap(); + let shard = read_shard(trusted_args).unwrap(); + let jsonrpc_call: String = get_json_request(shard, operation_call, encryption_key); + + debug!("get direct api"); + let direct_api = get_worker_api_direct(cli); + + debug!("setup sender and receiver"); + let (sender, receiver) = channel(); + direct_api.watch(jsonrpc_call, sender); + + debug!("waiting for rpc response"); + loop { + match receiver.recv() { + Ok(response) => { + debug!("received response"); + let response: RpcResponse = serde_json::from_str(&response).unwrap(); + if let Ok(return_value) = RpcReturnValue::from_hex(&response.result) { + debug!("successfully decoded rpc response: {:?}", return_value); + match return_value.status { + DirectRequestStatus::Error => { + debug!("request status is error"); + if let Ok(value) = String::decode(&mut return_value.value.as_slice()) { + println!("[Error] {}", value); + } + direct_api.close().unwrap(); + return None + }, + DirectRequestStatus::TrustedOperationStatus(status) => { + debug!("request status is: {:?}", status); + if let Ok(value) = Hash::decode(&mut return_value.value.as_slice()) { + println!("Trusted call {:?} is {:?}", value, status); + } + if connection_can_be_closed(status) { + direct_api.close().unwrap(); + } + }, + _ => { + debug!("request status is ignored"); + direct_api.close().unwrap(); + return None + }, + } + if !return_value.do_watch { + debug!("do watch is false, closing connection"); + direct_api.close().unwrap(); + return None + } + }; + }, + Err(e) => { + error!("failed to receive rpc response: {:?}", e); + direct_api.close().unwrap(); + return None + }, + }; + } +} + +pub(crate) fn get_json_request( + shard: ShardIdentifier, + operation_call: &TrustedOperation, + shielding_pubkey: sgx_crypto_helper::rsa3072::Rsa3072PubKey, +) -> String { + let operation_call_encrypted = shielding_pubkey.encrypt(&operation_call.encode()).unwrap(); + + // compose jsonrpc call + let request = Request { shard, cyphertext: operation_call_encrypted }; + RpcRequest::compose_jsonrpc_call( + "author_submitAndWatchExtrinsic".to_string(), + vec![request.to_hex()], + ) + .unwrap() +} + +pub(crate) fn wait_until( + receiver: &Receiver, + until: impl Fn(TrustedOperationStatus) -> bool, +) -> Option<(H256, Instant)> { + debug!("waiting for rpc response"); + loop { + match receiver.recv() { + Ok(response) => { + debug!("received response: {}", response); + let parse_result: Result = serde_json::from_str(&response); + if let Ok(response) = parse_result { + if let Ok(return_value) = RpcReturnValue::from_hex(&response.result) { + debug!("successfully decoded rpc response: {:?}", return_value); + match return_value.status { + DirectRequestStatus::Error => { + debug!("request status is error"); + if let Ok(value) = + String::decode(&mut return_value.value.as_slice()) + { + println!("[Error] {}", value); + } + return None + }, + DirectRequestStatus::TrustedOperationStatus(status) => { + debug!("request status is: {:?}", status); + if let Ok(value) = Hash::decode(&mut return_value.value.as_slice()) + { + println!("Trusted call {:?} is {:?}", value, status); + if until(status.clone()) { + return Some((value, Instant::now())) + } else if status == TrustedOperationStatus::Invalid { + error!("Invalid request"); + return None + } + } + }, + _ => { + debug!("request status is ignored"); + return None + }, + } + }; + } else { + error!("Could not parse response"); + }; + }, + Err(e) => { + error!("failed to receive rpc response: {:?}", e); + return None + }, + }; + } +} + +fn connection_can_be_closed(top_status: TrustedOperationStatus) -> bool { + !matches!( + top_status, + TrustedOperationStatus::Submitted + | TrustedOperationStatus::Future + | TrustedOperationStatus::Ready + | TrustedOperationStatus::Broadcast + ) +} + +#[allow(dead_code)] +#[derive(Decode)] +struct ProcessedParentchainBlockArgs { + signer: AccountId, + block_hash: H256, + merkle_root: H256, + block_number: BlockNumber, +} diff --git a/tee-worker/cli/ts_tests.sh b/tee-worker/cli/ts_tests.sh new file mode 100755 index 0000000000..0f35f62471 --- /dev/null +++ b/tee-worker/cli/ts_tests.sh @@ -0,0 +1,7 @@ +#!/bin/bash + +set -o pipefail + +cd /ts-tests +yarn install +yarn run test-identity diff --git a/tee-worker/cli/user_shielding_key.sh b/tee-worker/cli/user_shielding_key.sh new file mode 100755 index 0000000000..b60673e740 --- /dev/null +++ b/tee-worker/cli/user_shielding_key.sh @@ -0,0 +1,102 @@ +#!/bin/bash + +while getopts ":p:A:B:u:W:V:C:" opt; do + case $opt in + p) + NPORT=$OPTARG + ;; + A) + WORKER1PORT=$OPTARG + ;; + B) + WORKER2PORT=$OPTARG + ;; + u) + NODEURL=$OPTARG + ;; + V) + WORKER1URL=$OPTARG + ;; + W) + WORKER2URL=$OPTARG + ;; + C) + CLIENT_BIN=$OPTARG + ;; + esac +done + +# Using default port if none given as arguments. +NPORT=${NPORT:-9944} +NODEURL=${NODEURL:-"ws://127.0.0.1"} + +WORKER1PORT=${WORKER1PORT:-2000} +WORKER1URL=${WORKER1URL:-"wss://127.0.0.1"} + +CLIENT_BIN=${CLIENT_BIN:-"./../bin/integritee-cli"} + +echo "Using client binary $CLIENT_BIN" +echo "Using node uri $NODEURL:$NPORT" +echo "Using trusted-worker uri $WORKER1URL:$WORKER1PORT" +echo "" + +ACC=//Bob +KEY="22fc82db5b606998ad45099b7978b5b4f9dd4ea6017e57370ac56141caaabd12" + +CLIENT="$CLIENT_BIN -p $NPORT -P $WORKER1PORT -u $NODEURL -U $WORKER1URL" +echo "CLIENT is $CLIENT" + +echo "* Query on-chain enclave registry:" +$CLIENT list-workers +echo "" + +if [ "$READMRENCLAVE" = "file" ] +then + read MRENCLAVE <<< $(cat ~/mrenclave.b58) + echo "Reading MRENCLAVE from file: ${MRENCLAVE}" +else + # This will always take the first MRENCLAVE found in the registry !! + read MRENCLAVE <<< $($CLIENT list-workers | awk '/ MRENCLAVE: / { print $2; exit }') + echo "Reading MRENCLAVE from worker list: ${MRENCLAVE}" +fi +[[ -z $MRENCLAVE ]] && { echo "MRENCLAVE is empty. cannot continue" ; exit 1; } + +# indirect call that will be sent to the parachain, it will be synchronously handled +sleep 10 +echo "* Set $ACC 's shielding key to $KEY" +${CLIENT} set-user-shielding-key "$ACC" "$KEY" ${MRENCLAVE} +echo "" + +sleep 20 +echo "* Get $ACC 's shielding key" +ACTUAL_KEY=$($CLIENT trusted --mrenclave $MRENCLAVE --direct user-shielding-key $ACC) +echo "" + +if [ "$ACTUAL_KEY" = "$KEY" ]; then + echo "KEY identical: $KEY" + echo "test indirect call passed" +else + echo "KEY non-identical: expected: $KEY actual: $ACTUAL_KEY" + exit 1 +fi + +echo "------------------------------" +# direct call that will be asynchronously handled +KEY="8378193a4ce64180814bd60591d1054a04dbc4da02afde453799cd6888ee0c6c" +sleep 10 +echo "* Set $ACC 's shielding key to $KEY" +${CLIENT} trusted --mrenclave $MRENCLAVE --direct set-user-shielding-key-preflight "$ACC" "$KEY" +echo "" + +sleep 35 +echo "* Get $ACC 's shielding key" +ACTUAL_KEY=$($CLIENT trusted --mrenclave $MRENCLAVE --direct user-shielding-key $ACC) +echo "" + +if [ "$ACTUAL_KEY" = "$KEY" ]; then + echo "KEY identical: $KEY" + echo "test direct call passed" +else + echo "KEY non-identical: expected: $KEY actual: $ACTUAL_KEY" + exit 1 +fi diff --git a/tee-worker/core-primitives/attestation-handler/AttestationReportSigningCACert.pem b/tee-worker/core-primitives/attestation-handler/AttestationReportSigningCACert.pem new file mode 100644 index 0000000000..948b4c0cdd --- /dev/null +++ b/tee-worker/core-primitives/attestation-handler/AttestationReportSigningCACert.pem @@ -0,0 +1,31 @@ +-----BEGIN CERTIFICATE----- +MIIFSzCCA7OgAwIBAgIJANEHdl0yo7CUMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNV +BAYTAlVTMQswCQYDVQQIDAJDQTEUMBIGA1UEBwwLU2FudGEgQ2xhcmExGjAYBgNV +BAoMEUludGVsIENvcnBvcmF0aW9uMTAwLgYDVQQDDCdJbnRlbCBTR1ggQXR0ZXN0 +YXRpb24gUmVwb3J0IFNpZ25pbmcgQ0EwIBcNMTYxMTE0MTUzNzMxWhgPMjA0OTEy +MzEyMzU5NTlaMH4xCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJDQTEUMBIGA1UEBwwL +U2FudGEgQ2xhcmExGjAYBgNVBAoMEUludGVsIENvcnBvcmF0aW9uMTAwLgYDVQQD +DCdJbnRlbCBTR1ggQXR0ZXN0YXRpb24gUmVwb3J0IFNpZ25pbmcgQ0EwggGiMA0G +CSqGSIb3DQEBAQUAA4IBjwAwggGKAoIBgQCfPGR+tXc8u1EtJzLA10Feu1Wg+p7e +LmSRmeaCHbkQ1TF3Nwl3RmpqXkeGzNLd69QUnWovYyVSndEMyYc3sHecGgfinEeh +rgBJSEdsSJ9FpaFdesjsxqzGRa20PYdnnfWcCTvFoulpbFR4VBuXnnVLVzkUvlXT +L/TAnd8nIZk0zZkFJ7P5LtePvykkar7LcSQO85wtcQe0R1Raf/sQ6wYKaKmFgCGe +NpEJUmg4ktal4qgIAxk+QHUxQE42sxViN5mqglB0QJdUot/o9a/V/mMeH8KvOAiQ +byinkNndn+Bgk5sSV5DFgF0DffVqmVMblt5p3jPtImzBIH0QQrXJq39AT8cRwP5H +afuVeLHcDsRp6hol4P+ZFIhu8mmbI1u0hH3W/0C2BuYXB5PC+5izFFh/nP0lc2Lf +6rELO9LZdnOhpL1ExFOq9H/B8tPQ84T3Sgb4nAifDabNt/zu6MmCGo5U8lwEFtGM +RoOaX4AS+909x00lYnmtwsDVWv9vBiJCXRsCAwEAAaOByTCBxjBgBgNVHR8EWTBX +MFWgU6BRhk9odHRwOi8vdHJ1c3RlZHNlcnZpY2VzLmludGVsLmNvbS9jb250ZW50 +L0NSTC9TR1gvQXR0ZXN0YXRpb25SZXBvcnRTaWduaW5nQ0EuY3JsMB0GA1UdDgQW +BBR4Q3t2pn680K9+QjfrNXw7hwFRPDAfBgNVHSMEGDAWgBR4Q3t2pn680K9+Qjfr +NXw7hwFRPDAOBgNVHQ8BAf8EBAMCAQYwEgYDVR0TAQH/BAgwBgEB/wIBADANBgkq +hkiG9w0BAQsFAAOCAYEAeF8tYMXICvQqeXYQITkV2oLJsp6J4JAqJabHWxYJHGir +IEqucRiJSSx+HjIJEUVaj8E0QjEud6Y5lNmXlcjqRXaCPOqK0eGRz6hi+ripMtPZ +sFNaBwLQVV905SDjAzDzNIDnrcnXyB4gcDFCvwDFKKgLRjOB/WAqgscDUoGq5ZVi +zLUzTqiQPmULAQaB9c6Oti6snEFJiCQ67JLyW/E83/frzCmO5Ru6WjU4tmsmy8Ra +Ud4APK0wZTGtfPXU7w+IBdG5Ez0kE1qzxGQaL4gINJ1zMyleDnbuS8UicjJijvqA +152Sq049ESDz+1rRGc2NVEqh1KaGXmtXvqxXcTB+Ljy5Bw2ke0v8iGngFBPqCTVB +3op5KBG3RjbF6RRSzwzuWfL7QErNC8WEy5yDVARzTA5+xmBc388v9Dm21HGfcC8O +DD+gT9sSpssq0ascmvH49MOgjt1yoysLtdCtJW/9FZpoOypaHx0R+mJTLwPXVMrv +DaVzWh5aiEx+idkSGMnX +-----END CERTIFICATE----- diff --git a/tee-worker/core-primitives/attestation-handler/Cargo.toml b/tee-worker/core-primitives/attestation-handler/Cargo.toml new file mode 100644 index 0000000000..c18ac80099 --- /dev/null +++ b/tee-worker/core-primitives/attestation-handler/Cargo.toml @@ -0,0 +1,105 @@ +[package] +authors = ["Integritee AG "] +edition = "2021" +name = "itp-attestation-handler" +version = "0.8.0" + +[dependencies] +# crates-io no_std deps +arrayvec = { version = "0.7.1", default-features = false } +bit-vec = { version = "0.6", default-features = false } +codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } +hex = { version = "0.4.3", default-features = false, features = ["alloc"] } +itertools = { default-features = false, version = "0.10.1" } +log = { version = "0.4", default-features = false } + +# std only deps +base64 = { version = "0.13", features = ["alloc"], optional = true } +chrono = { version = "0.4.19", features = ["alloc"], optional = true } +rustls = { version = "0.19", optional = true } +serde_json = { version = "1.0", optional = true } +thiserror = { version = "1.0", optional = true } +webpki = { version = "0.21", optional = true } + +# mesalock +base64_sgx = { package = "base64", rev = "sgx_1.1.3", git = "https://github.com/mesalock-linux/rust-base64-sgx", optional = true } +chrono_sgx = { package = "chrono", git = "https://github.com/mesalock-linux/chrono-sgx", optional = true } +num-bigint = { optional = true, git = "https://github.com/mesalock-linux/num-bigint-sgx" } +rustls_sgx = { package = "rustls", rev = "sgx_1.1.3", features = ["dangerous_configuration"], git = "https://github.com/mesalock-linux/rustls", optional = true } +serde_json_sgx = { package = "serde_json", tag = "sgx_1.1.3", git = "https://github.com/mesalock-linux/serde-json-sgx", optional = true } +thiserror_sgx = { package = "thiserror", git = "https://github.com/mesalock-linux/thiserror-sgx", tag = "sgx_1.1.3", optional = true } +webpki-roots = { git = "https://github.com/mesalock-linux/webpki-roots", branch = "mesalock_sgx" } +webpki_sgx = { package = "webpki", git = "https://github.com/mesalock-linux/webpki", branch = "mesalock_sgx", optional = true } +yasna_sgx = { package = "yasna", optional = true, default-features = false, features = ["bit-vec", "num-bigint", "chrono", "mesalock_sgx"], git = "https://github.com/mesalock-linux/yasna.rs-sgx", rev = "sgx_1.1.3" } + +# sgx +sgx_rand = { branch = "master", git = "https://github.com/apache/teaclave-sgx-sdk.git", optional = true } +sgx_tcrypto = { branch = "master", git = "https://github.com/apache/teaclave-sgx-sdk.git", optional = true } +sgx_tse = { branch = "master", git = "https://github.com/apache/teaclave-sgx-sdk.git", optional = true } +sgx_tstd = { branch = "master", git = "https://github.com/apache/teaclave-sgx-sdk.git", features = ["untrusted_fs", "net", "backtrace"], optional = true } +sgx_types = { branch = "master", git = "https://github.com/apache/teaclave-sgx-sdk.git" } + +# local deps +itp-ocall-api = { path = "../ocall-api", default-features = false } +itp-settings = { path = "../settings" } +itp-sgx-crypto = { path = "../sgx/crypto", default-features = false } +itp-sgx-io = { path = "../sgx/io", default-features = false } +itp-types = { path = "../types", default-features = false } + +# integritee +httparse = { default-features = false, git = "https://github.com/integritee-network/httparse-sgx", branch = "sgx-experimental" } + + +# substrate deps +sp-core = { default-features = false, features = ["full_crypto"], git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.29" } +sp-runtime = { default-features = false, git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.29" } + +[features] +default = ["std"] +std = [ + # crates-io no_std + "arrayvec/std", + "codec/std", + "hex/std", + "log/std", + "itertools/use_std", + # optional std only + "base64", + "chrono", + "rustls", + "serde_json", + "thiserror", + "webpki", + # local + "itp-ocall-api/std", + "itp-sgx-io/std", + "itp-sgx-crypto/std", + "itp-types/std", + # substrate + "sp-core/std", + "sp-runtime/std", + # integritee + "httparse/std", +] + +sgx = [ + # sgx-only + "base64_sgx", + "chrono_sgx", + "rustls_sgx", + "serde_json_sgx", + "thiserror_sgx", + "webpki_sgx", + "yasna_sgx", + "sgx_tse", + "sgx_tstd", + "sgx_rand", + "sgx_tcrypto", + "num-bigint", + # local + "itp-sgx-io/sgx", + "itp-sgx-crypto/sgx", + # integritee + "httparse/mesalock_sgx", +] +test = [] diff --git a/tee-worker/core-primitives/attestation-handler/src/attestation_handler.rs b/tee-worker/core-primitives/attestation-handler/src/attestation_handler.rs new file mode 100644 index 0000000000..4db09fd596 --- /dev/null +++ b/tee-worker/core-primitives/attestation-handler/src/attestation_handler.rs @@ -0,0 +1,592 @@ +// Copyright 2022 Integritee AG and Supercomputing Systems AG +// Copyright (C) 2017-2019 Baidu, Inc. All Rights Reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in +// the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Baidu, Inc., nor the names of its +// contributors may be used to endorse or promote products derived +// from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#[cfg(all(not(feature = "std"), feature = "sgx"))] +use crate::sgx_reexport_prelude::*; + +use crate::{cert, Error as EnclaveError, Error, Result as EnclaveResult}; +use codec::Encode; +use core::default::Default; +use itertools::Itertools; +use itp_ocall_api::EnclaveAttestationOCallApi; +use itp_settings::{ + files::{RA_API_KEY_FILE, RA_DUMP_CERT_DER_FILE, RA_SPID_FILE}, + worker::MR_ENCLAVE_SIZE, +}; +use itp_sgx_crypto::Ed25519Seal; +use itp_sgx_io as io; +use itp_sgx_io::StaticSealedIO; + +use log::*; +use sgx_rand::{os, Rng}; +use sgx_tcrypto::{rsgx_sha256_slice, SgxEccHandle}; +use sgx_tse::{rsgx_create_report, rsgx_verify_report}; +use sgx_types::{ + c_int, sgx_epid_group_id_t, sgx_quote_nonce_t, sgx_quote_sign_type_t, sgx_report_data_t, + sgx_spid_t, sgx_status_t, sgx_target_info_t, SgxResult, +}; +use sp_core::Pair; +use std::{ + borrow::ToOwned, + format, + io::{Read, Write}, + net::TcpStream, + println, str, + string::{String, ToString}, + sync::Arc, + vec::Vec, +}; + +pub const DEV_HOSTNAME: &str = "api.trustedservices.intel.com"; + +#[cfg(feature = "production")] +pub const SIGRL_SUFFIX: &str = "/sgx/attestation/v4/sigrl/"; +#[cfg(feature = "production")] +pub const REPORT_SUFFIX: &str = "/sgx/attestation/v4/report"; + +#[cfg(not(feature = "production"))] +pub const SIGRL_SUFFIX: &str = "/sgx/dev/attestation/v4/sigrl/"; +#[cfg(not(feature = "production"))] +pub const REPORT_SUFFIX: &str = "/sgx/dev/attestation/v4/report"; + +/// Trait to provide an abstraction to the attestation logic +pub trait AttestationHandler { + /// Generates an encoded remote attestation certificate. + /// If skip_ra is set, it will not perform a remote attestation via IAS + /// but instead generate a mock certificate. + fn perform_ra(&self, skip_ra: bool) -> EnclaveResult>; + + /// Get the measurement register value of the enclave + fn get_mrenclave(&self) -> EnclaveResult<[u8; MR_ENCLAVE_SIZE]>; + + /// Write the remote attestation report to the disk + fn dump_ra_to_disk(&self) -> EnclaveResult<()>; + + /// Create the remote attestation report and encapsulate it in a DER certificate + /// Returns a pair consisting of (private key DER, certificate DER) + fn create_ra_report_and_signature( + &self, + sign_type: sgx_quote_sign_type_t, + skip_ra: bool, + ) -> EnclaveResult<(Vec, Vec)>; +} + +pub struct IasAttestationHandler { + ocall_api: Arc, +} + +impl AttestationHandler for IasAttestationHandler +where + OCallApi: EnclaveAttestationOCallApi, +{ + fn perform_ra(&self, skip_ra: bool) -> EnclaveResult> { + // Our certificate is unlinkable. + let sign_type = sgx_quote_sign_type_t::SGX_UNLINKABLE_SIGNATURE; + + // FIXME: should call `create_ra_report_and_signature` in skip_ra mode as well: + // https://github.com/integritee-network/worker/issues/321. + let cert_der = if !skip_ra { + match self.create_ra_report_and_signature(sign_type, skip_ra) { + Ok((_key_der, cert_der)) => cert_der, + Err(e) => return Err(e), + } + } else { + self.get_mrenclave()?.encode() + }; + + Ok(cert_der) + } + + fn get_mrenclave(&self) -> EnclaveResult<[u8; MR_ENCLAVE_SIZE]> { + match self.ocall_api.get_mrenclave_of_self() { + Ok(m) => Ok(m.m), + Err(e) => Err(EnclaveError::Sgx(e)), + } + } + + fn dump_ra_to_disk(&self) -> EnclaveResult<()> { + // our certificate is unlinkable + let sign_type = sgx_quote_sign_type_t::SGX_UNLINKABLE_SIGNATURE; + + let (_key_der, cert_der) = match self.create_ra_report_and_signature(sign_type, false) { + Ok(r) => r, + Err(e) => return Err(e), + }; + + if let Err(err) = io::write(&cert_der, RA_DUMP_CERT_DER_FILE) { + error!( + " [Enclave] failed to write RA file ({}), status: {:?}", + RA_DUMP_CERT_DER_FILE, err + ); + return Err(Error::IoError(err)) + } + info!(" [Enclave] dumped ra cert to {}", RA_DUMP_CERT_DER_FILE); + Ok(()) + } + + fn create_ra_report_and_signature( + &self, + sign_type: sgx_quote_sign_type_t, + skip_ra: bool, + ) -> EnclaveResult<(Vec, Vec)> { + let chain_signer = Ed25519Seal::unseal_from_static_file()?; + info!("[Enclave Attestation] Ed25519 pub raw : {:?}", chain_signer.public().0); + + info!(" [Enclave] Generate keypair"); + let ecc_handle = SgxEccHandle::new(); + let _result = ecc_handle.open(); + let (prv_k, pub_k) = ecc_handle.create_key_pair()?; + info!(" [Enclave] Generate ephemeral ECDSA keypair successful"); + debug!(" pubkey X is {:02x}", pub_k.gx.iter().format("")); + debug!(" pubkey Y is {:02x}", pub_k.gy.iter().format("")); + + let payload = if !skip_ra { + info!(" [Enclave] Create attestation report"); + let (attn_report, sig, cert) = + match self.create_attestation_report(&chain_signer.public().0, sign_type) { + Ok(r) => r, + Err(e) => { + error!(" [Enclave] Error in create_attestation_report: {:?}", e); + return Err(e.into()) + }, + }; + println!(" [Enclave] Create attestation report successful"); + debug!(" attn_report = {:?}", attn_report); + debug!(" sig = {:?}", sig); + debug!(" cert = {:?}", cert); + + // concat the information + attn_report + "|" + &sig + "|" + &cert + } else { + Default::default() + }; + + // generate an ECC certificate + info!(" [Enclave] Generate ECC Certificate"); + let (key_der, cert_der) = match cert::gen_ecc_cert(payload, &prv_k, &pub_k, &ecc_handle) { + Ok(r) => r, + Err(e) => { + error!(" [Enclave] gen_ecc_cert failed: {:?}", e); + return Err(e.into()) + }, + }; + + let _ = ecc_handle.close(); + info!(" [Enclave] Generate ECC Certificate successful"); + Ok((key_der, cert_der)) + } +} + +impl IasAttestationHandler +where + OCallApi: EnclaveAttestationOCallApi, +{ + pub fn new(ocall_api: Arc) -> Self { + Self { ocall_api } + } + + fn parse_response_attn_report(&self, resp: &[u8]) -> EnclaveResult<(String, String, String)> { + debug!(" [Enclave] Entering parse_response_attn_report"); + let mut headers = [httparse::EMPTY_HEADER; 16]; + let mut respp = httparse::Response::new(&mut headers); + let result = respp.parse(resp); + debug!(" [Enclave] respp.parse result {:?}", result); + + self.log_resp_code(&mut respp.code); + + let mut len_num: u32 = 0; + + let mut sig = String::new(); + let mut cert = String::new(); + let mut attn_report = String::new(); + + for i in 0..respp.headers.len() { + let h = respp.headers[i]; + //println!("{} : {}", h.name, str::from_utf8(h.value).unwrap()); + match h.name { + "Content-Length" => { + let len_str = String::from_utf8(h.value.to_vec()) + .map_err(|e| EnclaveError::Other(e.into()))?; + len_num = len_str.parse::().map_err(|e| EnclaveError::Other(e.into()))?; + debug!(" [Enclave] Content length = {}", len_num); + }, + "X-IASReport-Signature" => + sig = String::from_utf8(h.value.to_vec()) + .map_err(|e| EnclaveError::Other(e.into()))?, + "X-IASReport-Signing-Certificate" => + cert = String::from_utf8(h.value.to_vec()) + .map_err(|e| EnclaveError::Other(e.into()))?, + _ => (), + } + } + + // Remove %0A from cert, and only obtain the signing cert + cert = cert.replace("%0A", ""); + cert = cert::percent_decode(cert)?; + let v: Vec<&str> = cert.split("-----").collect(); + let sig_cert = v[2].to_string(); + + if len_num != 0 { + // The unwrap is safe. It resolves to the https::Status' unwrap function which only panics + // if the the response is not complete, which cannot happen if the result is Ok(). + let header_len = result.map_err(|e| EnclaveError::Other(e.into()))?.unwrap(); + let resp_body = &resp[header_len..]; + attn_report = + String::from_utf8(resp_body.to_vec()).map_err(|e| EnclaveError::Other(e.into()))?; + debug!(" [Enclave] Attestation report = {}", attn_report); + } + + // len_num == 0 + Ok((attn_report, sig, sig_cert)) + } + + fn log_resp_code(&self, resp_code: &mut Option) { + let msg = match resp_code { + Some(200) => "OK Operation Successful", + Some(401) => "Unauthorized Failed to authenticate or authorize request.", + Some(404) => "Not Found GID does not refer to a valid EPID group ID.", + Some(500) => "Internal error occurred", + Some(503) => + "Service is currently not able to process the request (due to + a temporary overloading or maintenance). This is a + temporary state – the same request can be repeated after + some time. ", + _ => { + error!("DBG:{:?}", resp_code); + "Unknown error occured" + }, + }; + debug!(" [Enclave] msg = {}", msg); + } + + fn parse_response_sigrl(&self, resp: &[u8]) -> EnclaveResult> { + debug!(" [Enclave] Entering parse_response_sigrl"); + let mut headers = [httparse::EMPTY_HEADER; 16]; + let mut respp = httparse::Response::new(&mut headers); + let result = respp.parse(resp); + debug!(" [Enclave] Parse result {:?}", result); + debug!(" [Enclave] Parse response {:?}", respp); + + self.log_resp_code(&mut respp.code); + + let mut len_num: u32 = 0; + + for i in 0..respp.headers.len() { + let h = respp.headers[i]; + if h.name == "content-length" { + let len_str = String::from_utf8(h.value.to_vec()) + .map_err(|e| EnclaveError::Other(e.into()))?; + len_num = len_str.parse::().map_err(|e| EnclaveError::Other(e.into()))?; + debug!(" [Enclave] Content length = {}", len_num); + } + } + + if len_num != 0 { + // The unwrap is safe. It resolves to the https::Status' unwrap function which only panics + // if the the response is not complete, which cannot happen if the result is Ok(). + let header_len = result.map_err(|e| EnclaveError::Other(e.into()))?.unwrap(); + let resp_body = &resp[header_len..]; + debug!(" [Enclave] Base64-encoded SigRL: {:?}", resp_body); + + let resp_str = str::from_utf8(resp_body).map_err(|e| EnclaveError::Other(e.into()))?; + return base64::decode(resp_str).map_err(|e| EnclaveError::Other(e.into())) + } + + // len_num == 0 + Ok(Vec::new()) + } + + fn make_ias_client_config() -> rustls::ClientConfig { + let mut config = rustls::ClientConfig::new(); + + config.root_store.add_server_trust_anchors(&webpki_roots::TLS_SERVER_ROOTS); + config + } + + fn get_sigrl_from_intel(&self, fd: c_int, gid: u32) -> EnclaveResult> { + debug!(" [Enclave] Entering get_sigrl_from_intel. fd = {:?}", fd); + let config = Self::make_ias_client_config(); + //let sigrl_arg = SigRLArg { group_id : gid }; + //let sigrl_req = sigrl_arg.to_httpreq(); + let ias_key = Self::get_ias_api_key()?; + + let req = format!("GET {}{:08x} HTTP/1.1\r\nHOST: {}\r\nOcp-Apim-Subscription-Key: {}\r\nConnection: Close\r\n\r\n", + SIGRL_SUFFIX, + gid, + DEV_HOSTNAME, + ias_key); + debug!(" [Enclave] request = {}", req); + + let dns_name = webpki::DNSNameRef::try_from_ascii_str(DEV_HOSTNAME) + .map_err(|e| EnclaveError::Other(e.into()))?; + let mut sess = rustls::ClientSession::new(&Arc::new(config), dns_name); + let mut sock = TcpStream::new(fd)?; + let mut tls = rustls::Stream::new(&mut sess, &mut sock); + + let _result = tls.write(req.as_bytes()); + let mut plaintext = Vec::new(); + + debug!(" [Enclave] tls.write complete"); + + tls.read_to_end(&mut plaintext)?; + + debug!(" [Enclave] tls.read_to_end complete"); + let resp_string = + String::from_utf8(plaintext.clone()).map_err(|e| EnclaveError::Other(e.into()))?; + + debug!(" [Enclave] resp_string = {}", resp_string); + + self.parse_response_sigrl(&plaintext) + } + + // TODO: support pse + fn get_report_from_intel( + &self, + fd: c_int, + quote: Vec, + ) -> EnclaveResult<(String, String, String)> { + debug!(" [Enclave] Entering get_report_from_intel. fd = {:?}", fd); + let config = Self::make_ias_client_config(); + let encoded_quote = base64::encode("e[..]); + let encoded_json = format!("{{\"isvEnclaveQuote\":\"{}\"}}\r\n", encoded_quote); + + let ias_key = Self::get_ias_api_key()?; + + let req = format!("POST {} HTTP/1.1\r\nHOST: {}\r\nOcp-Apim-Subscription-Key:{}\r\nContent-Length:{}\r\nContent-Type: application/json\r\nConnection: close\r\n\r\n{}", + REPORT_SUFFIX, + DEV_HOSTNAME, + ias_key, + encoded_json.len(), + encoded_json); + debug!(" [Enclave] Req = {}", req); + let dns_name = webpki::DNSNameRef::try_from_ascii_str(DEV_HOSTNAME).map_err(|e| { + error!("Invalid DEV_HOSTNAME"); + EnclaveError::Other(e.into()) + })?; + let mut sess = rustls::ClientSession::new(&Arc::new(config), dns_name); + let mut sock = TcpStream::new(fd)?; + let mut tls = rustls::Stream::new(&mut sess, &mut sock); + + let _result = tls.write(req.as_bytes()); + let mut plaintext = Vec::new(); + + debug!(" [Enclave] tls.write complete"); + + tls.read_to_end(&mut plaintext)?; + debug!(" [Enclave] tls.read_to_end complete"); + let resp_string = String::from_utf8(plaintext.clone()).map_err(|e| { + error!(" [Enclave] error decoding tls answer to string"); + EnclaveError::Other(e.into()) + })?; + + debug!(" [Enclave] resp_string = {}", resp_string); + + self.parse_response_attn_report(&plaintext) + } + + fn as_u32_le(&self, array: [u8; 4]) -> u32 { + u32::from(array[0]) + + (u32::from(array[1]) << 8) + + (u32::from(array[2]) << 16) + + (u32::from(array[3]) << 24) + } + + fn create_attestation_report( + &self, + pub_k: &[u8; 32], + sign_type: sgx_quote_sign_type_t, + ) -> SgxResult<(String, String, String)> { + // Workflow: + // (1) ocall to get the target_info structure (ti) and epid group id (eg) + // (1.5) get sigrl + // (2) call sgx_create_report with ti+data, produce an sgx_report_t + // (3) ocall to sgx_get_quote to generate (*mut sgx-quote_t, uint32_t) + + // (1) get ti + eg + let init_quote = self.ocall_api.sgx_init_quote()?; + + let epid_group_id: sgx_epid_group_id_t = init_quote.1; + let target_info: sgx_target_info_t = init_quote.0; + + debug!(" [Enclave] EPID group id = {:?}", epid_group_id); + + let eg_num = self.as_u32_le(epid_group_id); + + // (1.5) get sigrl + let ias_socket = self.ocall_api.get_ias_socket()?; + + info!(" [Enclave] ias_sock = {}", ias_socket); + + // Now sigrl_vec is the revocation list, a vec + let sigrl_vec: Vec = self.get_sigrl_from_intel(ias_socket, eg_num)?; + + // (2) Generate the report + let mut report_data: sgx_report_data_t = sgx_report_data_t::default(); + report_data.d[..32].clone_from_slice(&pub_k[..]); + + let report = match rsgx_create_report(&target_info, &report_data) { + Ok(r) => { + debug!( + " [Enclave] Report creation successful. mr_signer.m = {:x?}", + r.body.mr_signer.m + ); + r + }, + Err(e) => { + error!(" [Enclave] Report creation failed. {:?}", e); + return Err(e) + }, + }; + + let mut quote_nonce = sgx_quote_nonce_t { rand: [0; 16] }; + let mut os_rng = os::SgxRng::new().map_err(|e| EnclaveError::Other(e.into()))?; + os_rng.fill_bytes(&mut quote_nonce.rand); + + // (3) Generate the quote + // Args: + // 1. sigrl: ptr + len + // 2. report: ptr 432bytes + // 3. linkable: u32, unlinkable=0, linkable=1 + // 4. spid: sgx_spid_t ptr 16bytes + // 5. sgx_quote_nonce_t ptr 16bytes + // 6. p_sig_rl + sigrl size ( same to sigrl) + // 7. [out]p_qe_report need further check + // 8. [out]p_quote + // 9. quote_size + + let spid: sgx_spid_t = Self::load_spid(RA_SPID_FILE)?; + + let quote_result = + self.ocall_api.get_quote(sigrl_vec, report, sign_type, spid, quote_nonce)?; + + let qe_report = quote_result.0; + let quote_content = quote_result.1; + + // Added 09-28-2018 + // Perform a check on qe_report to verify if the qe_report is valid + match rsgx_verify_report(&qe_report) { + Ok(()) => debug!(" [Enclave] rsgx_verify_report success!"), + Err(x) => { + error!(" [Enclave] rsgx_verify_report failed. {:?}", x); + return Err(x) + }, + } + + // Check if the qe_report is produced on the same platform + if target_info.mr_enclave.m != qe_report.body.mr_enclave.m + || target_info.attributes.flags != qe_report.body.attributes.flags + || target_info.attributes.xfrm != qe_report.body.attributes.xfrm + { + error!(" [Enclave] qe_report does not match current target_info!"); + return Err(sgx_status_t::SGX_ERROR_UNEXPECTED) + } + + debug!(" [Enclave] qe_report check success"); + + // Check qe_report to defend against replay attack + // The purpose of p_qe_report is for the ISV enclave to confirm the QUOTE + // it received is not modified by the untrusted SW stack, and not a replay. + // The implementation in QE is to generate a REPORT targeting the ISV + // enclave (target info from p_report) , with the lower 32Bytes in + // report.data = SHA256(p_nonce||p_quote). The ISV enclave can verify the + // p_qe_report and report.data to confirm the QUOTE has not be modified and + // is not a replay. It is optional. + + // need to call this a second time (first time is when we get the sigrl revocation list) + // (has some internal state that needs to be reset)! + let ias_socket = self.ocall_api.get_ias_socket()?; + + let mut rhs_vec: Vec = quote_nonce.rand.to_vec(); + rhs_vec.extend("e_content); + let rhs_hash = rsgx_sha256_slice(&rhs_vec[..])?; + let lhs_hash = &qe_report.body.report_data.d[..32]; + + debug!(" [Enclave] rhs hash = {:02X}", rhs_hash.iter().format("")); + debug!(" [Enclave] lhs hash = {:02X}", lhs_hash.iter().format("")); + + if rhs_hash != lhs_hash { + error!(" [Enclave] Quote is tampered!"); + return Err(sgx_status_t::SGX_ERROR_UNEXPECTED) + } + + let (attn_report, sig, cert) = self.get_report_from_intel(ias_socket, quote_content)?; + Ok((attn_report, sig, cert)) + } + + fn load_spid(filename: &str) -> SgxResult { + match io::read_to_string(filename).map(|contents| decode_spid(&contents)) { + Ok(r) => r, + Err(e) => { + error!("Failed to load SPID: {:?}", e); + Err(sgx_status_t::SGX_ERROR_UNEXPECTED) + }, + } + } + + fn get_ias_api_key() -> EnclaveResult { + io::read_to_string(RA_API_KEY_FILE) + .map(|key| key.trim_end().to_owned()) + .map_err(|e| EnclaveError::Other(e.into())) + } +} + +fn decode_spid(hex_encoded_string: &str) -> SgxResult { + let mut spid = sgx_spid_t::default(); + let hex = hex_encoded_string.trim(); + + if hex.len() < itp_settings::files::SPID_MIN_LENGTH { + error!( + "Input spid length ({}) is incorrect, minimum length required is {}", + hex.len(), + itp_settings::files::SPID_MIN_LENGTH + ); + return Err(sgx_status_t::SGX_ERROR_UNEXPECTED) + } + + let decoded_vec = hex::decode(hex).map_err(|_| sgx_status_t::SGX_ERROR_UNEXPECTED)?; + + spid.id.copy_from_slice(&decoded_vec[..16]); + Ok(spid) +} + +#[cfg(feature = "test")] +pub mod tests { + + use super::*; + + pub fn decode_spid_works() { + let spid_encoded = "F39ABCF95015A5BF6C7D360EF5035E12"; + let expected_spid = sgx_spid_t { + id: [243, 154, 188, 249, 80, 21, 165, 191, 108, 125, 54, 14, 245, 3, 94, 18], + }; + + let decoded_spid = decode_spid(spid_encoded).unwrap(); + assert_eq!(decoded_spid.id, expected_spid.id); + } +} diff --git a/tee-worker/core-primitives/attestation-handler/src/cert.rs b/tee-worker/core-primitives/attestation-handler/src/cert.rs new file mode 100644 index 0000000000..c5077d9f50 --- /dev/null +++ b/tee-worker/core-primitives/attestation-handler/src/cert.rs @@ -0,0 +1,451 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +#[cfg(all(not(feature = "std"), feature = "sgx"))] +use crate::sgx_reexport_prelude::*; + +use crate::{Error as EnclaveError, Result as EnclaveResult}; +use arrayvec::ArrayVec; +use chrono::DateTime; +use itertools::Itertools; +use itp_ocall_api::EnclaveAttestationOCallApi; +use log::*; +use serde_json::Value; +use sgx_types::{ + sgx_platform_info_t, sgx_quote_t, sgx_status_t, SgxResult, SGX_PLATFORM_INFO_SIZE, +}; +use std::{ + io::BufReader, + ptr, str, + string::String, + time::{SystemTime, UNIX_EPOCH}, + vec::Vec, +}; + +type SignatureAlgorithms = &'static [&'static webpki::SignatureAlgorithm]; +static SUPPORTED_SIG_ALGS: SignatureAlgorithms = &[ + &webpki::ECDSA_P256_SHA256, + &webpki::ECDSA_P256_SHA384, + &webpki::ECDSA_P384_SHA256, + &webpki::ECDSA_P384_SHA384, + &webpki::RSA_PSS_2048_8192_SHA256_LEGACY_KEY, + &webpki::RSA_PSS_2048_8192_SHA384_LEGACY_KEY, + &webpki::RSA_PSS_2048_8192_SHA512_LEGACY_KEY, + &webpki::RSA_PKCS1_2048_8192_SHA256, + &webpki::RSA_PKCS1_2048_8192_SHA384, + &webpki::RSA_PKCS1_2048_8192_SHA512, + &webpki::RSA_PKCS1_3072_8192_SHA384, +]; + +pub const CERTEXPIRYDAYS: i64 = 90i64; +pub const IAS_REPORT_CA: &[u8] = include_bytes!("../AttestationReportSigningCACert.pem"); + +#[cfg(feature = "sgx")] +pub use sgx::*; + +#[cfg(feature = "sgx")] +pub mod sgx { + use super::*; + use bit_vec::BitVec; + use chrono::{Duration, TimeZone, Utc as TzUtc}; + use num_bigint::BigUint; + use sgx_tcrypto::SgxEccHandle; + use sgx_types::{sgx_ec256_private_t, sgx_ec256_public_t}; + use yasna::models::ObjectIdentifier; + + const ISSUER: &str = "Integritee"; + const SUBJECT: &str = "Integritee ephemeral"; + + pub fn gen_ecc_cert( + payload: String, + prv_k: &sgx_ec256_private_t, + pub_k: &sgx_ec256_public_t, + ecc_handle: &SgxEccHandle, + ) -> Result<(Vec, Vec), sgx_status_t> { + // Generate public key bytes since both DER will use it + let mut pub_key_bytes: Vec = vec![4]; + let mut pk_gx = pub_k.gx; + pk_gx.reverse(); + let mut pk_gy = pub_k.gy; + pk_gy.reverse(); + pub_key_bytes.extend_from_slice(&pk_gx); + pub_key_bytes.extend_from_slice(&pk_gy); + + // Generate Certificate DER + let cert_der = yasna::construct_der(|writer| { + writer.write_sequence(|writer| { + writer.next().write_sequence(|writer| { + // Certificate Version + writer.next().write_tagged(yasna::Tag::context(0), |writer| { + writer.write_i8(2); + }); + // Certificate Serial Number (unused but required) + writer.next().write_u8(1); + // Signature Algorithm: ecdsa-with-SHA256 + writer.next().write_sequence(|writer| { + writer + .next() + .write_oid(&ObjectIdentifier::from_slice(&[1, 2, 840, 10045, 4, 3, 2])); + }); + // Issuer: CN=MesaTEE (unused but required) + writer.next().write_sequence(|writer| { + writer.next().write_set(|writer| { + writer.next().write_sequence(|writer| { + writer + .next() + .write_oid(&ObjectIdentifier::from_slice(&[2, 5, 4, 3])); + writer.next().write_utf8_string(ISSUER); + }); + }); + }); + // Validity: Issuing/Expiring Time (unused but required) + let now = SystemTime::now().duration_since(UNIX_EPOCH).unwrap(); + let issue_ts = TzUtc.timestamp(now.as_secs() as i64, 0); + let expire = now + Duration::days(CERTEXPIRYDAYS).to_std().unwrap(); + let expire_ts = TzUtc.timestamp(expire.as_secs() as i64, 0); + writer.next().write_sequence(|writer| { + writer + .next() + .write_utctime(&yasna::models::UTCTime::from_datetime(&issue_ts)); + writer + .next() + .write_utctime(&yasna::models::UTCTime::from_datetime(&expire_ts)); + }); + // Subject: CN=MesaTEE (unused but required) + writer.next().write_sequence(|writer| { + writer.next().write_set(|writer| { + writer.next().write_sequence(|writer| { + writer + .next() + .write_oid(&ObjectIdentifier::from_slice(&[2, 5, 4, 3])); + writer.next().write_utf8_string(SUBJECT); + }); + }); + }); + writer.next().write_sequence(|writer| { + // Public Key Algorithm + writer.next().write_sequence(|writer| { + // id-ecPublicKey + writer.next().write_oid(&ObjectIdentifier::from_slice(&[ + 1, 2, 840, 10045, 2, 1, + ])); + // prime256v1 + writer.next().write_oid(&ObjectIdentifier::from_slice(&[ + 1, 2, 840, 10045, 3, 1, 7, + ])); + }); + // Public Key + writer.next().write_bitvec(&BitVec::from_bytes(&pub_key_bytes)); + }); + // Certificate V3 Extension + writer.next().write_tagged(yasna::Tag::context(3), |writer| { + writer.write_sequence(|writer| { + writer.next().write_sequence(|writer| { + writer.next().write_oid(&ObjectIdentifier::from_slice(&[ + 2, 16, 840, 1, 113_730, 1, 13, + ])); + writer.next().write_bytes(&payload.into_bytes()); + }); + }); + }); + }); + // Signature Algorithm: ecdsa-with-SHA256 + writer.next().write_sequence(|writer| { + writer + .next() + .write_oid(&ObjectIdentifier::from_slice(&[1, 2, 840, 10045, 4, 3, 2])); + }); + // Signature + let sig = { + let tbs = &writer.buf[4..]; + ecc_handle.ecdsa_sign_slice(tbs, prv_k).unwrap() + }; + let sig_der = yasna::construct_der(|writer| { + writer.write_sequence(|writer| { + let mut sig_x = sig.x; + sig_x.reverse(); + let mut sig_y = sig.y; + sig_y.reverse(); + writer.next().write_biguint(&BigUint::from_slice(&sig_x)); + writer.next().write_biguint(&BigUint::from_slice(&sig_y)); + }); + }); + writer.next().write_bitvec(&BitVec::from_bytes(&sig_der)); + }); + }); + + // Generate Private Key DER + let key_der = yasna::construct_der(|writer| { + writer.write_sequence(|writer| { + writer.next().write_u8(0); + writer.next().write_sequence(|writer| { + writer + .next() + .write_oid(&ObjectIdentifier::from_slice(&[1, 2, 840, 10045, 2, 1])); + writer + .next() + .write_oid(&ObjectIdentifier::from_slice(&[1, 2, 840, 10045, 3, 1, 7])); + }); + let inner_key_der = yasna::construct_der(|writer| { + writer.write_sequence(|writer| { + writer.next().write_u8(1); + let mut prv_k_r = prv_k.r; + prv_k_r.reverse(); + writer.next().write_bytes(&prv_k_r); + writer.next().write_tagged(yasna::Tag::context(1), |writer| { + writer.write_bitvec(&BitVec::from_bytes(&pub_key_bytes)); + }); + }); + }); + writer.next().write_bytes(&inner_key_der); + }); + }); + + Ok((key_der, cert_der)) + } +} + +pub fn percent_decode(orig: String) -> EnclaveResult { + let v: Vec<&str> = orig.split('%').collect(); + let mut ret = String::new(); + ret.push_str(v[0]); + if v.len() > 1 { + for s in v[1..].iter() { + ret.push(u8::from_str_radix(&s[0..2], 16).map_err(|e| EnclaveError::Other(e.into()))? + as char); + ret.push_str(&s[2..]); + } + } + Ok(ret) +} + +// FIXME: This code is redundant with the host call of the integritee-node +pub fn verify_mra_cert(cert_der: &[u8], attestation_ocall: &A) -> SgxResult<()> +where + A: EnclaveAttestationOCallApi, +{ + // Before we reach here, Webpki already verified the cert is properly signed + + // Search for Public Key prime256v1 OID + let prime256v1_oid = &[0x06, 0x08, 0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x03, 0x01, 0x07]; + let mut offset = cert_der + .windows(prime256v1_oid.len()) + .position(|window| window == prime256v1_oid) + .ok_or(sgx_status_t::SGX_ERROR_UNEXPECTED)?; + offset += 11; // 10 + TAG (0x03) + + // Obtain Public Key length + let mut len = cert_der[offset] as usize; + if len > 0x80 { + len = (cert_der[offset + 1] as usize) * 0x100 + (cert_der[offset + 2] as usize); + offset += 2; + } + + // Obtain Public Key + offset += 1; + let pub_k = cert_der[offset + 2..offset + len].to_vec(); // skip "00 04" + + // Search for Netscape Comment OID + let ns_cmt_oid = &[0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x86, 0xF8, 0x42, 0x01, 0x0D]; + let mut offset = cert_der + .windows(ns_cmt_oid.len()) + .position(|window| window == ns_cmt_oid) + .ok_or(sgx_status_t::SGX_ERROR_UNEXPECTED)?; + offset += 12; // 11 + TAG (0x04) + + // Obtain Netscape Comment length + let mut len = cert_der[offset] as usize; + if len > 0x80 { + len = (cert_der[offset + 1] as usize) * 0x100 + (cert_der[offset + 2] as usize); + offset += 2; + } + + // Obtain Netscape Comment + offset += 1; + let payload = cert_der[offset..offset + len].to_vec(); + + // Extract each field + let mut iter = payload.split(|x| *x == 0x7C); + let attn_report_raw = iter.next().ok_or(sgx_status_t::SGX_ERROR_UNEXPECTED)?; + let sig_raw = iter.next().ok_or(sgx_status_t::SGX_ERROR_UNEXPECTED)?; + let sig = base64::decode(sig_raw).map_err(|e| EnclaveError::Other(e.into()))?; + + let sig_cert_raw = iter.next().ok_or(sgx_status_t::SGX_ERROR_UNEXPECTED)?; + let sig_cert_dec = base64::decode_config(sig_cert_raw, base64::STANDARD) + .map_err(|e| EnclaveError::Other(e.into()))?; + let sig_cert = webpki::EndEntityCert::from(&sig_cert_dec).expect("Bad DER"); + + // Verify if the signing cert is issued by Intel CA + let mut ias_ca_stripped = IAS_REPORT_CA.to_vec(); + ias_ca_stripped.retain(|&x| x != 0x0d && x != 0x0a); + let head_len = "-----BEGIN CERTIFICATE-----".len(); + let tail_len = "-----END CERTIFICATE-----".len(); + let full_len = ias_ca_stripped.len(); + let ias_ca_core: &[u8] = &ias_ca_stripped[head_len..full_len - tail_len]; + let ias_cert_dec = base64::decode_config(ias_ca_core, base64::STANDARD) + .map_err(|e| EnclaveError::Other(e.into()))?; + + let mut ca_reader = BufReader::new(IAS_REPORT_CA); + + let mut root_store = rustls::RootCertStore::empty(); + root_store.add_pem_file(&mut ca_reader).expect("Failed to add CA"); + + let trust_anchors: Vec = + root_store.roots.iter().map(|cert| cert.to_trust_anchor()).collect(); + + let now_func = webpki::Time::try_from(SystemTime::now()); + + match sig_cert.verify_is_valid_tls_server_cert( + SUPPORTED_SIG_ALGS, + &webpki::TLSServerTrustAnchors(&trust_anchors), + &[ias_cert_dec.as_slice()], + now_func.map_err(|_e| EnclaveError::Time)?, + ) { + Ok(_) => info!("Cert is good"), + Err(e) => { + error!("Cert verification error {:?}", e); + return Err(sgx_status_t::SGX_ERROR_UNEXPECTED) + }, + } + + // Verify the signature against the signing cert + match sig_cert.verify_signature(&webpki::RSA_PKCS1_2048_8192_SHA256, attn_report_raw, &sig) { + Ok(_) => info!("Signature good"), + Err(e) => { + error!("Signature verification error {:?}", e); + return Err(sgx_status_t::SGX_ERROR_UNEXPECTED) + }, + } + + verify_attn_report(attn_report_raw, pub_k, attestation_ocall) +} + +pub fn verify_attn_report( + report_raw: &[u8], + pub_k: Vec, + attestation_ocall: &A, +) -> SgxResult<()> +where + A: EnclaveAttestationOCallApi, +{ + // Verify attestation report + // 1. Check timestamp is within 24H (90day is recommended by Intel) + let attn_report: Value = + serde_json::from_slice(report_raw).map_err(|e| EnclaveError::Other(e.into()))?; + if let Value::String(time) = &attn_report["timestamp"] { + let time_fixed = time.clone() + "+0000"; + let ts = DateTime::parse_from_str(&time_fixed, "%Y-%m-%dT%H:%M:%S%.f%z") + .map_err(|e| EnclaveError::Other(e.into()))? + .timestamp(); + let now = SystemTime::now() + .duration_since(UNIX_EPOCH) + .map_err(|e| EnclaveError::Other(e.into()))? + .as_secs() as i64; + info!("Time diff = {}", now - ts); + } else { + error!("Failed to fetch timestamp from attestation report"); + return Err(sgx_status_t::SGX_ERROR_UNEXPECTED) + } + + // 2. Verify quote status (mandatory field) + if let Value::String(quote_status) = &attn_report["isvEnclaveQuoteStatus"] { + debug!("isvEnclaveQuoteStatus = {}", quote_status); + match quote_status.as_ref() { + "OK" => (), + "GROUP_OUT_OF_DATE" | "GROUP_REVOKED" | "CONFIGURATION_NEEDED" => { + // Verify platformInfoBlob for further info if status not OK + if let Value::String(pib) = &attn_report["platformInfoBlob"] { + let mut buf = ArrayVec::<_, SGX_PLATFORM_INFO_SIZE>::new(); + + // the TLV Header (4 bytes/8 hexes) should be skipped + let n = (pib.len() - 8) / 2; + for i in 0..n { + buf.try_push( + u8::from_str_radix(&pib[(i * 2 + 8)..(i * 2 + 10)], 16) + .map_err(|e| EnclaveError::Other(e.into()))?, + ) + .map_err(|e| { + error!("failed to push element to platform info blob buffer, exceeding buffer size ({})", e); + sgx_status_t::SGX_ERROR_UNEXPECTED + })?; + } + + // ArrayVec .into_inner() requires that all elements are occupied by a value + // if that's not the case, the following error will occur + let platform_info = buf.into_inner().map_err(|e| { + error!("Failed to extract platform info from InfoBlob, result does not contain enough elements (require: {}, found: {})", e.capacity(), e.len()); + sgx_status_t::SGX_ERROR_UNEXPECTED + })?; + + attestation_ocall.get_update_info(sgx_platform_info_t { platform_info }, 1)?; + } else { + error!("Failed to fetch platformInfoBlob from attestation report"); + return Err(sgx_status_t::SGX_ERROR_UNEXPECTED) + } + }, + _ => return Err(sgx_status_t::SGX_ERROR_UNEXPECTED), + } + } else { + error!("Failed to fetch isvEnclaveQuoteStatus from attestation report"); + return Err(sgx_status_t::SGX_ERROR_UNEXPECTED) + } + + // 3. Verify quote body + if let Value::String(quote_raw) = &attn_report["isvEnclaveQuoteBody"] { + let quote = base64::decode(quote_raw).map_err(|e| EnclaveError::Other(e.into()))?; + debug!("Quote = {:?}", quote); + // TODO: lack security check here + let sgx_quote: sgx_quote_t = unsafe { ptr::read(quote.as_ptr() as *const _) }; + + let ti = attestation_ocall.get_mrenclave_of_self()?; + if sgx_quote.report_body.mr_enclave.m != ti.m { + error!( + "mr_enclave is not equal to self {:?} != {:?}", + sgx_quote.report_body.mr_enclave.m, ti.m + ); + return Err(sgx_status_t::SGX_ERROR_UNEXPECTED) + } + + // ATTENTION + // DO SECURITY CHECK ON DEMAND + // DO SECURITY CHECK ON DEMAND + // DO SECURITY CHECK ON DEMAND + + // Curly braces to copy `unaligned_references` of packed fields into properly aligned temporary: + // https://github.com/rust-lang/rust/issues/82523 + debug!("sgx quote version = {}", { sgx_quote.version }); + debug!("sgx quote signature type = {}", { sgx_quote.sign_type }); + debug!( + "sgx quote report_data = {:02x}", + sgx_quote.report_body.report_data.d.iter().format("") + ); + debug!( + "sgx quote mr_enclave = {:02x}", + sgx_quote.report_body.mr_enclave.m.iter().format("") + ); + debug!("sgx quote mr_signer = {:02x}", sgx_quote.report_body.mr_signer.m.iter().format("")); + debug!("Anticipated public key = {:02x}", pub_k.iter().format("")); + if sgx_quote.report_body.report_data.d.to_vec() == pub_k.to_vec() { + info!("Mutual RA done!"); + } + } else { + error!("Failed to fetch isvEnclaveQuoteBody from attestation report"); + return Err(sgx_status_t::SGX_ERROR_UNEXPECTED) + } + + Ok(()) +} diff --git a/tee-worker/core-primitives/attestation-handler/src/error.rs b/tee-worker/core-primitives/attestation-handler/src/error.rs new file mode 100644 index 0000000000..e681ce8c2a --- /dev/null +++ b/tee-worker/core-primitives/attestation-handler/src/error.rs @@ -0,0 +1,64 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +#[cfg(all(not(feature = "std"), feature = "sgx"))] +use crate::sgx_reexport_prelude::*; + +use sgx_types::sgx_status_t; +use std::boxed::Box; + +pub type Result = core::result::Result; + +/// Parentchain block importer error. +#[derive(Debug, thiserror::Error)] +pub enum Error { + #[error("SGX error, status: {0}")] + Sgx(sgx_status_t), + #[error("{0}")] + IoError(#[from] std::io::Error), + #[error("Crypto error: {0}")] + Crypto(itp_sgx_crypto::Error), + #[error("Error specifying time")] + Time, + #[error(transparent)] + Other(#[from] Box), +} + +impl From for Error { + fn from(sgx_status: sgx_status_t) -> Self { + Self::Sgx(sgx_status) + } +} + +impl From for Error { + fn from(error: itp_sgx_crypto::error::Error) -> Self { + Self::Crypto(error) + } +} + +impl From for sgx_status_t { + /// return sgx_status for top level enclave functions + fn from(error: Error) -> sgx_status_t { + match error { + Error::Sgx(status) => status, + _ => { + log::error!("Returning error {:?} as sgx unexpected.", error); + sgx_status_t::SGX_ERROR_UNEXPECTED + }, + } + } +} diff --git a/tee-worker/core-primitives/attestation-handler/src/lib.rs b/tee-worker/core-primitives/attestation-handler/src/lib.rs new file mode 100644 index 0000000000..bbb1f1911b --- /dev/null +++ b/tee-worker/core-primitives/attestation-handler/src/lib.rs @@ -0,0 +1,49 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +#![cfg_attr(not(feature = "std"), no_std)] + +#[cfg(all(feature = "std", feature = "sgx"))] +compile_error!("feature \"std\" and feature \"sgx\" cannot be enabled at the same time"); + +#[cfg(all(not(feature = "std"), feature = "sgx"))] +#[macro_use] +extern crate sgx_tstd as std; + +// re-export module to properly feature gate sgx and regular std environment +#[cfg(all(not(feature = "std"), feature = "sgx"))] +pub mod sgx_reexport_prelude { + pub use base64_sgx as base64; + pub use chrono_sgx as chrono; + pub use rustls_sgx as rustls; + pub use serde_json_sgx as serde_json; + pub use thiserror_sgx as thiserror; + pub use webpki_sgx as webpki; + pub use yasna_sgx as yasna; +} + +#[cfg(all(not(feature = "std"), feature = "sgx"))] +pub mod attestation_handler; + +pub mod cert; + +pub mod error; + +#[cfg(all(not(feature = "std"), feature = "sgx"))] +pub use attestation_handler::{AttestationHandler, IasAttestationHandler, DEV_HOSTNAME}; + +pub use error::{Error, Result}; diff --git a/tee-worker/core-primitives/block-import-queue/Cargo.toml b/tee-worker/core-primitives/block-import-queue/Cargo.toml new file mode 100644 index 0000000000..c13eb9e267 --- /dev/null +++ b/tee-worker/core-primitives/block-import-queue/Cargo.toml @@ -0,0 +1,32 @@ +[package] +authors = ["Integritee AG "] +edition = "2021" +name = "itp-block-import-queue" +version = "0.8.0" + +[dependencies] +# sgx dependencies +sgx_tstd = { branch = "master", git = "https://github.com/apache/teaclave-sgx-sdk.git", optional = true } +sgx_types = { branch = "master", git = "https://github.com/apache/teaclave-sgx-sdk.git" } + +# sgx enabled external libraries +thiserror_sgx = { package = "thiserror", git = "https://github.com/mesalock-linux/thiserror-sgx", tag = "sgx_1.1.3", optional = true } + +# crates.io std-only compatible external libraries (make sure these versions match with the sgx-enabled ones above) +thiserror = { version = "1.0", optional = true } + +# crates.io no-std compatible libraries + +[features] +default = ["std"] +sgx = [ + # sgx + "sgx_tstd", + # sgx enabled external libraries + "thiserror_sgx", +] +std = [ + # no-std compatible libraries + # std compatible external libraries + "thiserror", +] diff --git a/tee-worker/core-primitives/block-import-queue/src/block_import_queue.rs b/tee-worker/core-primitives/block-import-queue/src/block_import_queue.rs new file mode 100644 index 0000000000..3aa740b463 --- /dev/null +++ b/tee-worker/core-primitives/block-import-queue/src/block_import_queue.rs @@ -0,0 +1,263 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +//! Block import queue implementation + +#[cfg(feature = "sgx")] +use std::sync::SgxRwLock as RwLock; + +#[cfg(feature = "std")] +use std::sync::RwLock; + +use crate::{ + error::{Error, Result}, + PeekBlockQueue, PopFromBlockQueue, PushToBlockQueue, +}; +use std::{collections::VecDeque, vec::Vec}; + +/// Block import queue. +/// +/// Uses RwLock internally to guard against concurrent access and ensure all operations are atomic. +pub struct BlockImportQueue { + queue: RwLock>, +} + +impl BlockImportQueue { + pub fn is_empty(&self) -> Result { + let queue_lock = self.queue.read().map_err(|_| Error::PoisonedLock)?; + Ok(queue_lock.is_empty()) + } +} + +impl Default for BlockImportQueue { + fn default() -> Self { + BlockImportQueue { queue: Default::default() } + } +} + +impl PushToBlockQueue for BlockImportQueue { + fn push_multiple(&self, blocks: Vec) -> Result<()> { + let mut queue_lock = self.queue.write().map_err(|_| Error::PoisonedLock)?; + queue_lock.extend(blocks); + Ok(()) + } + + fn push_single(&self, block: SignedBlock) -> Result<()> { + let mut queue_lock = self.queue.write().map_err(|_| Error::PoisonedLock)?; + queue_lock.push_back(block); + Ok(()) + } +} + +impl PopFromBlockQueue for BlockImportQueue { + type BlockType = SignedBlock; + + fn pop_all_but_last(&self) -> Result> { + let mut queue_lock = self.queue.write().map_err(|_| Error::PoisonedLock)?; + let queue_length = queue_lock.len(); + if queue_length < 2 { + return Ok(Vec::::default()) + } + Ok(queue_lock.drain(..queue_length - 1).collect::>()) + } + + fn pop_all(&self) -> Result> { + let mut queue_lock = self.queue.write().map_err(|_| Error::PoisonedLock)?; + Ok(queue_lock.drain(..).collect::>()) + } + + fn pop_until(&self, predicate: Predicate) -> Result> + where + Predicate: FnMut(&Self::BlockType) -> bool, + { + let mut queue_lock = self.queue.write().map_err(|_| Error::PoisonedLock)?; + match queue_lock.iter().position(predicate) { + None => Ok(Vec::new()), + Some(p) => Ok(queue_lock.drain(..p + 1).collect::>()), + } + } + + fn pop_front(&self) -> Result> { + let mut queue_lock = self.queue.write().map_err(|_| Error::PoisonedLock)?; + Ok(queue_lock.pop_front()) + } +} + +impl PeekBlockQueue for BlockImportQueue +where + SignedBlock: Clone, +{ + type BlockType = SignedBlock; + + fn peek_find(&self, predicate: Predicate) -> Result> + where + Predicate: Fn(&Self::BlockType) -> bool, + { + let queue_lock = self.queue.read().map_err(|_| Error::PoisonedLock)?; + let maybe_block = queue_lock.iter().find(|&b| predicate(b)); + Ok(maybe_block.cloned()) + } + + fn peek_last(&self) -> Result> { + let queue_lock = self.queue.read().map_err(|_| Error::PoisonedLock)?; + Ok(queue_lock.back().cloned()) + } + + fn peek_queue_size(&self) -> Result { + let queue_lock = self.queue.read().map_err(|_| Error::PoisonedLock)?; + Ok(queue_lock.len()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use core::assert_matches::assert_matches; + + type TestBlock = u32; + + #[test] + fn default_queue_is_empty() { + let queue = BlockImportQueue::::default(); + assert!(queue.is_empty().unwrap()); + } + + #[test] + fn pop_all_on_default_returns_empty_vec() { + let queue = BlockImportQueue::::default(); + assert!(queue.pop_all().unwrap().is_empty()); + } + + #[test] + fn after_inserting_queue_is_not_empty() { + let queue = BlockImportQueue::::default(); + queue.push_single(TestBlock::default()).unwrap(); + assert!(!queue.is_empty().unwrap()); + } + + #[test] + fn pop_all_after_inserting_leaves_empty_queue() { + let queue = BlockImportQueue::::default(); + queue + .push_multiple(vec![TestBlock::default(), TestBlock::default(), TestBlock::default()]) + .unwrap(); + + let all_popped = queue.pop_all().unwrap(); + assert_eq!(3, all_popped.len()); + assert!(queue.is_empty().unwrap()); + } + + #[test] + fn pop_all_except_last_on_default_returns_empty_vec() { + let queue = BlockImportQueue::::default(); + assert!(queue.pop_all_but_last().unwrap().is_empty()); + } + + #[test] + fn pop_all_except_last_with_single_element_returns_empty_vec() { + let queue = BlockImportQueue::::default(); + queue.push_single(TestBlock::default()).unwrap(); + assert!(queue.pop_all_but_last().unwrap().is_empty()); + } + + #[test] + fn pop_all_except_last_with_multiple_elements_returns_all_but_last_inserted() { + let queue = BlockImportQueue::::default(); + queue.push_multiple(vec![1, 3, 5, 7]).unwrap(); + assert_eq!(3, queue.pop_all_but_last().unwrap().len()); + assert!(!queue.is_empty().unwrap()); + assert_eq!(7, queue.pop_all().unwrap()[0]); + } + + #[test] + fn pop_until_returns_empty_vec_if_nothing_matches() { + let queue = BlockImportQueue::::default(); + queue.push_multiple(vec![1, 3, 5, 7]).unwrap(); + + let popped_elements = queue.pop_until(|i| i > &10u32).unwrap(); + assert!(popped_elements.is_empty()); + } + + #[test] + fn pop_until_returns_elements_until_and_including_match() { + let queue = BlockImportQueue::::default(); + queue.push_multiple(vec![1, 2, 3, 10]).unwrap(); + + assert_eq!(queue.pop_until(|i| i == &3).unwrap(), vec![1, 2, 3]); + } + + #[test] + fn pop_until_returns_all_elements_if_last_matches() { + let queue = BlockImportQueue::::default(); + queue.push_multiple(vec![1, 2, 3, 10]).unwrap(); + + assert_eq!(queue.pop_until(|i| i == &10).unwrap(), vec![1, 2, 3, 10]); + } + + #[test] + fn pop_until_returns_first_element_if_it_matches() { + let queue = BlockImportQueue::::default(); + queue.push_single(4).unwrap(); + assert_eq!(queue.pop_until(|i| i == &4).unwrap(), vec![4]) + } + + #[test] + fn pop_front_returns_none_if_queue_is_empty() { + let queue = BlockImportQueue::::default(); + assert_matches!(queue.pop_front().unwrap(), None); + } + + #[test] + fn pop_front_works() { + let queue = BlockImportQueue::::default(); + queue.push_multiple(vec![1, 2, 3, 5]).unwrap(); + assert_eq!(queue.pop_front().unwrap(), Some(1)); + assert_eq!(queue.pop_front().unwrap(), Some(2)); + assert_eq!(queue.pop_front().unwrap(), Some(3)); + assert_eq!(queue.pop_front().unwrap(), Some(5)); + assert_eq!(queue.pop_front().unwrap(), None); + } + + #[test] + fn peek_find_works() { + let queue = BlockImportQueue::::default(); + queue.push_multiple(vec![1, 2, 3, 5]).unwrap(); + + assert_eq!(None, queue.peek_find(|i| i == &4).unwrap()); + assert!(queue.peek_find(|i| i == &1).unwrap().is_some()); + assert!(queue.peek_find(|i| i == &5).unwrap().is_some()); + } + + #[test] + fn peek_find_on_empty_queue_returns_none() { + let queue = BlockImportQueue::::default(); + assert_eq!(None, queue.peek_find(|i| i == &1).unwrap()); + } + + #[test] + fn peek_last_works() { + let queue = BlockImportQueue::::default(); + queue.push_multiple(vec![1, 2, 3, 5, 6, 9, 10]).unwrap(); + assert_eq!(queue.peek_last().unwrap(), Some(10)); + } + + #[test] + fn peek_last_on_empty_queue_returns_none() { + let queue = BlockImportQueue::::default(); + assert_eq!(None, queue.peek_last().unwrap()); + } +} diff --git a/tee-worker/core-primitives/block-import-queue/src/error.rs b/tee-worker/core-primitives/block-import-queue/src/error.rs new file mode 100644 index 0000000000..c1492cf550 --- /dev/null +++ b/tee-worker/core-primitives/block-import-queue/src/error.rs @@ -0,0 +1,41 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +#[cfg(all(not(feature = "std"), feature = "sgx"))] +use crate::sgx_reexport_prelude::*; + +use sgx_types::sgx_status_t; +use std::boxed::Box; + +pub type Result = core::result::Result; + +/// Parentchain block importer error. +#[derive(Debug, thiserror::Error)] +pub enum Error { + #[error("SGX error, status: {0}")] + Sgx(sgx_status_t), + #[error("Queue lock is poisoned")] + PoisonedLock, + #[error(transparent)] + Other(#[from] Box), +} + +impl From for Error { + fn from(sgx_status: sgx_status_t) -> Self { + Self::Sgx(sgx_status) + } +} diff --git a/tee-worker/core-primitives/block-import-queue/src/lib.rs b/tee-worker/core-primitives/block-import-queue/src/lib.rs new file mode 100644 index 0000000000..bfbc022ad5 --- /dev/null +++ b/tee-worker/core-primitives/block-import-queue/src/lib.rs @@ -0,0 +1,86 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ +//! Queueing of block imports. + +#![cfg_attr(not(feature = "std"), no_std)] +#![cfg_attr(test, feature(assert_matches))] + +#[cfg(all(feature = "std", feature = "sgx"))] +compile_error!("feature \"std\" and feature \"sgx\" cannot be enabled at the same time"); + +#[cfg(all(not(feature = "std"), feature = "sgx"))] +extern crate sgx_tstd as std; + +// re-export module to properly feature gate sgx and regular std environment +#[cfg(all(not(feature = "std"), feature = "sgx"))] +pub mod sgx_reexport_prelude { + pub use thiserror_sgx as thiserror; +} + +pub mod block_import_queue; +pub mod error; + +pub use block_import_queue::*; + +use error::Result; +use std::vec::Vec; + +/// Trait to push blocks to an import queue. +pub trait PushToBlockQueue { + /// Push multiple blocks to the queue, ordering from the Vec is preserved. + fn push_multiple(&self, blocks: Vec) -> Result<()>; + + /// Push a single block to the queue. + fn push_single(&self, block: BlockType) -> Result<()>; +} + +/// Trait to pop blocks from the import queue. +pub trait PopFromBlockQueue { + type BlockType; + + /// Pop (i.e. removes and returns) all but the last block from the import queue. + fn pop_all_but_last(&self) -> Result>; + + /// Pop (i.e. removes and returns) all blocks from the import queue. + fn pop_all(&self) -> Result>; + + /// Pop (front) until specified block is found. If no block matches, empty Vec is returned. + fn pop_until(&self, predicate: Predicate) -> Result> + where + Predicate: Fn(&Self::BlockType) -> bool; + + /// Pop (front) queue. Returns None if queue is empty. + fn pop_front(&self) -> Result>; +} + +/// Trait to peek blocks in the import queue without altering the queue. +pub trait PeekBlockQueue { + type BlockType: Clone; + + /// Search the queue with a given predicate and return a reference to the first element that matches. + /// Returns None if nothing matches. + fn peek_find(&self, predicate: Predicate) -> Result> + where + Predicate: Fn(&Self::BlockType) -> bool; + + /// Peeks the last element in the queue (aka the newest one, last to be popped). + /// Returns None if queue is empty. + fn peek_last(&self) -> Result>; + + /// Peek the queue size (i.e. number of elements the queue contains). + fn peek_queue_size(&self) -> Result; +} diff --git a/tee-worker/core-primitives/component-container/Cargo.toml b/tee-worker/core-primitives/component-container/Cargo.toml new file mode 100644 index 0000000000..6e7b283691 --- /dev/null +++ b/tee-worker/core-primitives/component-container/Cargo.toml @@ -0,0 +1,26 @@ +[package] +authors = ["Integritee AG "] +edition = "2021" +name = "itp-component-container" +version = "0.8.0" + +[dependencies] +# sgx dependencies +sgx_tstd = { branch = "master", git = "https://github.com/apache/teaclave-sgx-sdk.git", optional = true } + +# sgx enabled external libraries +thiserror_sgx = { package = "thiserror", git = "https://github.com/mesalock-linux/thiserror-sgx", tag = "sgx_1.1.3", optional = true } + +# std compatible external libraries (make sure these versions match with the sgx-enabled ones above) +thiserror = { version = "1.0", optional = true } + +[features] +default = ["std"] +sgx = [ + # sgx + "sgx_tstd", + "thiserror_sgx", +] +std = [ + "thiserror", +] diff --git a/tee-worker/core-primitives/component-container/src/atomic_container.rs b/tee-worker/core-primitives/component-container/src/atomic_container.rs new file mode 100644 index 0000000000..3f52ab291a --- /dev/null +++ b/tee-worker/core-primitives/component-container/src/atomic_container.rs @@ -0,0 +1,100 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +//! Container for a generic item, held by an AtomicPtr. + +#[cfg(feature = "sgx")] +use std::sync::SgxMutex as Mutex; + +#[cfg(feature = "std")] +use std::sync::Mutex; + +use std::{ + default::Default, + sync::{ + atomic::{AtomicPtr, Ordering}, + Arc, + }, +}; + +/// Generic atomic container that holds an item in a container. +pub struct AtomicContainer { + atomic_ptr: AtomicPtr<()>, +} + +impl AtomicContainer { + pub const fn new() -> Self { + AtomicContainer { atomic_ptr: AtomicPtr::new(0 as *mut ()) } + } + + /// Store and item in the container. + pub fn store(&self, item: T) { + let pool_ptr = Arc::new(Mutex::::new(item)); + let ptr = Arc::into_raw(pool_ptr); + self.atomic_ptr.store(ptr as *mut (), Ordering::SeqCst); + } + + /// Load an item from the container, returning a mutex. + pub fn load(&self) -> Option<&Mutex> { + let ptr = self.atomic_ptr.load(Ordering::SeqCst) as *mut Mutex; + if ptr.is_null() { + None + } else { + Some(unsafe { &*ptr }) + } + } +} + +impl Default for AtomicContainer { + fn default() -> Self { + Self::new() + } +} + +#[cfg(test)] +pub mod tests { + + use super::*; + use std::{ + ops::Deref, + string::{String, ToString}, + vec::Vec, + }; + + #[derive(PartialEq, Eq, Clone, Debug)] + struct TestPayload { + name: String, + data: Vec, + } + + #[test] + pub fn store_and_load_works() { + let atomic_container = AtomicContainer::new(); + + let test_payload = TestPayload { + name: "Payload".to_string(), + data: Vec::from("lots_of_data_to_be_stored".as_bytes()), + }; + + atomic_container.store(test_payload.clone()); + + let retrieved_mutex = atomic_container.load::().unwrap().lock().unwrap(); + let retrieved_payload = retrieved_mutex.deref(); + + assert_eq!(&test_payload, retrieved_payload); + } +} diff --git a/tee-worker/core-primitives/component-container/src/component_container.rs b/tee-worker/core-primitives/component-container/src/component_container.rs new file mode 100644 index 0000000000..ec0a16d50e --- /dev/null +++ b/tee-worker/core-primitives/component-container/src/component_container.rs @@ -0,0 +1,100 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +//! Generic component containers. + +#[cfg(feature = "sgx")] +use std::sync::SgxMutex as Mutex; + +#[cfg(feature = "std")] +use std::sync::Mutex; + +use crate::{ + atomic_container::AtomicContainer, + error::{Error, Result}, +}; +use std::{ + format, + marker::PhantomData, + string::{String, ToString}, + sync::Arc, +}; + +/// Trait to initialize a generic component. +pub trait ComponentInitializer { + type ComponentType; + + fn initialize(&self, component: Arc); +} + +/// Trait to retrieve a generic component. +pub trait ComponentGetter { + type ComponentType; + + /// Try to get a specific component, returns `None` if component has not been initialized. + fn get(&self) -> Result>; +} + +/// Workaround to make `new()` a `const fn`. +/// Is required in order to have the `ComponentContainer` in a static variable. +struct Invariant(T); + +/// Component container implementation. Can be used in a global static context. +pub struct ComponentContainer { + container: AtomicContainer, + component_name: &'static str, + _phantom: PhantomData>, +} + +impl ComponentContainer { + /// Create a new container instance. + /// + /// Has to be `const` in order to be used in a `static` context. + pub const fn new(component_name: &'static str) -> Self { + ComponentContainer { + container: AtomicContainer::new(), + component_name, + _phantom: PhantomData, + } + } +} + +impl ComponentInitializer for ComponentContainer { + type ComponentType = Component; + + fn initialize(&self, component: Arc) { + self.container.store(component) + } +} + +impl ToString for ComponentContainer { + fn to_string(&self) -> String { + format!("{} component", self.component_name) + } +} + +impl ComponentGetter for ComponentContainer { + type ComponentType = Component; + + fn get(&self) -> Result> { + let component_mutex: &Mutex> = self + .container + .load() + .ok_or_else(|| Error::ComponentNotInitialized(self.to_string()))?; + Ok(component_mutex.lock().expect("Lock poisoning").clone()) + } +} diff --git a/tee-worker/core-primitives/component-container/src/error.rs b/tee-worker/core-primitives/component-container/src/error.rs new file mode 100644 index 0000000000..9ca0ac0b20 --- /dev/null +++ b/tee-worker/core-primitives/component-container/src/error.rs @@ -0,0 +1,32 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +#[cfg(all(not(feature = "std"), feature = "sgx"))] +use crate::sgx_reexport_prelude::*; + +use std::{boxed::Box, string::String}; + +pub type Result = core::result::Result; + +/// extrinsics factory error +#[derive(Debug, thiserror::Error)] +pub enum Error { + #[error("Component is not initialized: {0}")] + ComponentNotInitialized(String), + #[error(transparent)] + Other(#[from] Box), +} diff --git a/tee-worker/core-primitives/component-container/src/lib.rs b/tee-worker/core-primitives/component-container/src/lib.rs new file mode 100644 index 0000000000..9c684e4361 --- /dev/null +++ b/tee-worker/core-primitives/component-container/src/lib.rs @@ -0,0 +1,36 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +#![cfg_attr(not(feature = "std"), no_std)] + +#[cfg(all(feature = "std", feature = "sgx"))] +compile_error!("feature \"std\" and feature \"sgx\" cannot be enabled at the same time"); + +#[cfg(all(not(feature = "std"), feature = "sgx"))] +extern crate sgx_tstd as std; + +// re-export module to properly feature gate sgx and regular std environment +#[cfg(all(not(feature = "std"), feature = "sgx"))] +pub mod sgx_reexport_prelude { + pub use thiserror_sgx as thiserror; +} + +mod atomic_container; +pub mod component_container; +pub mod error; + +pub use component_container::*; diff --git a/tee-worker/core-primitives/enclave-api/Cargo.toml b/tee-worker/core-primitives/enclave-api/Cargo.toml new file mode 100644 index 0000000000..460489591a --- /dev/null +++ b/tee-worker/core-primitives/enclave-api/Cargo.toml @@ -0,0 +1,25 @@ +[package] +authors = ["Integritee AG "] +edition = "2021" +name = "itp-enclave-api" +version = "0.9.0" + +[dependencies] +codec = { package = "parity-scale-codec", version = "3.0.0", features = ["derive"] } +log = "0.4" +serde_json = "1.0" +thiserror = "1.0.25" + +sgx_crypto_helper = { branch = "master", git = "https://github.com/apache/teaclave-sgx-sdk.git" } +sgx_types = { branch = "master", git = "https://github.com/apache/teaclave-sgx-sdk.git" } +sgx_urts = { branch = "master", git = "https://github.com/apache/teaclave-sgx-sdk.git" } + +frame-support = { git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.29" } +sp-core = { git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.29" } +sp-finality-grandpa = { git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.29" } +sp-runtime = { git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.29" } + +itc-parentchain = { path = "../../core/parentchain/parentchain-crate" } +itp-enclave-api-ffi = { path = "ffi" } +itp-settings = { path = "../settings" } +itp-types = { path = "../types" } diff --git a/tee-worker/core-primitives/enclave-api/ffi/Cargo.toml b/tee-worker/core-primitives/enclave-api/ffi/Cargo.toml new file mode 100644 index 0000000000..cb8afc2ac7 --- /dev/null +++ b/tee-worker/core-primitives/enclave-api/ffi/Cargo.toml @@ -0,0 +1,9 @@ +[package] +edition = "2021" +name = "itp-enclave-api-ffi" +version = "0.9.0" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +sgx_types = { branch = "master", git = "https://github.com/apache/teaclave-sgx-sdk.git" } diff --git a/tee-worker/core-primitives/enclave-api/ffi/build.rs b/tee-worker/core-primitives/enclave-api/ffi/build.rs new file mode 100644 index 0000000000..f4d28dc290 --- /dev/null +++ b/tee-worker/core-primitives/enclave-api/ffi/build.rs @@ -0,0 +1,42 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + Copyright (C) 2017-2019 Baidu, Inc. All Rights Reserved. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +use std::env; + +fn main() { + let sdk_dir = env::var("SGX_SDK").unwrap_or_else(|_| "/opt/intel/sgxsdk".to_string()); + let is_sim = env::var("SGX_MODE").unwrap_or_else(|_| "HW".to_string()); + + // NOTE: if the crate is a workspace member rustc-paths are relative from the root directory + println!("cargo:rustc-link-search=native=./lib"); + println!("cargo:rustc-link-lib=static=Enclave_u"); + + println!("cargo:rustc-link-search=native={}/lib64", sdk_dir); + println!("cargo:rustc-link-lib=static=sgx_uprotected_fs"); + match is_sim.as_ref() { + "SW" => { + println!("cargo:rustc-link-lib=dylib=sgx_urts_sim"); + println!("cargo:rustc-link-lib=dylib=sgx_uae_service_sim"); + }, + _ => { + // HW by default + println!("cargo:rustc-link-lib=dylib=sgx_urts"); + println!("cargo:rustc-link-lib=dylib=sgx_uae_service"); + }, + } +} diff --git a/tee-worker/core-primitives/enclave-api/ffi/src/lib.rs b/tee-worker/core-primitives/enclave-api/ffi/src/lib.rs new file mode 100644 index 0000000000..7b3d1d90d4 --- /dev/null +++ b/tee-worker/core-primitives/enclave-api/ffi/src/lib.rs @@ -0,0 +1,146 @@ +///! FFI's that call into the enclave. These functions need to be added to the +/// enclave edl file and be implemented within the enclave. +use sgx_types::{c_int, sgx_enclave_id_t, sgx_quote_sign_type_t, sgx_status_t}; + +extern "C" { + + pub fn init( + eid: sgx_enclave_id_t, + retval: *mut sgx_status_t, + mu_ra_addr: *const u8, + mu_ra_addr_size: u32, + untrusted_worker_addr: *const u8, + untrusted_worker_addr_size: u32, + ) -> sgx_status_t; + + pub fn init_enclave_sidechain_components( + eid: sgx_enclave_id_t, + retval: *mut sgx_status_t, + ) -> sgx_status_t; + + pub fn init_direct_invocation_server( + eid: sgx_enclave_id_t, + retval: *mut sgx_status_t, + server_addr: *const u8, + server_addr_size: u32, + ) -> sgx_status_t; + + pub fn init_parentchain_components( + eid: sgx_enclave_id_t, + retval: *mut sgx_status_t, + params: *const u8, + params_size: usize, + latest_header: *mut u8, + latest_header_size: usize, + ) -> sgx_status_t; + + pub fn init_shard( + eid: sgx_enclave_id_t, + retval: *mut sgx_status_t, + shard: *const u8, + shard_size: u32, + ) -> sgx_status_t; + + pub fn trigger_parentchain_block_import( + eid: sgx_enclave_id_t, + retval: *mut sgx_status_t, + ) -> sgx_status_t; + + pub fn execute_trusted_calls(eid: sgx_enclave_id_t, retval: *mut sgx_status_t) -> sgx_status_t; + + pub fn sync_parentchain( + eid: sgx_enclave_id_t, + retval: *mut sgx_status_t, + blocks: *const u8, + blocks_size: usize, + nonce: *const u32, + ) -> sgx_status_t; + + pub fn set_nonce( + eid: sgx_enclave_id_t, + retval: *mut sgx_status_t, + nonce: *const u32, + ) -> sgx_status_t; + + pub fn set_node_metadata( + eid: sgx_enclave_id_t, + retval: *mut sgx_status_t, + node_metadata: *const u8, + node_metadata_size: u32, + ) -> sgx_status_t; + + pub fn get_rsa_encryption_pubkey( + eid: sgx_enclave_id_t, + retval: *mut sgx_status_t, + pubkey: *mut u8, + pubkey_size: u32, + ) -> sgx_status_t; + + pub fn get_ecc_signing_pubkey( + eid: sgx_enclave_id_t, + retval: *mut sgx_status_t, + pubkey: *mut u8, + pubkey_size: u32, + ) -> sgx_status_t; + + pub fn get_mrenclave( + eid: sgx_enclave_id_t, + retval: *mut sgx_status_t, + mrenclave: *mut u8, + mrenclave_size: u32, + ) -> sgx_status_t; + + pub fn perform_ra( + eid: sgx_enclave_id_t, + retval: *mut sgx_status_t, + w_url: *const u8, + w_url_size: u32, + unchecked_extrinsic: *mut u8, + unchecked_extrinsic_size: u32, + skip_ra: c_int, + ) -> sgx_status_t; + + pub fn dump_ra_to_disk(eid: sgx_enclave_id_t, retval: *mut sgx_status_t) -> sgx_status_t; + + pub fn test_main_entrance(eid: sgx_enclave_id_t, retval: *mut sgx_status_t) -> sgx_status_t; + + pub fn call_rpc_methods( + eid: sgx_enclave_id_t, + retval: *mut sgx_status_t, + request: *const u8, + request_len: u32, + response: *mut u8, + response_len: u32, + ) -> sgx_status_t; + + pub fn update_market_data_xt( + eid: sgx_enclave_id_t, + retval: *mut sgx_status_t, + crypto_currency: *const u8, + crypto_currency_size: u32, + fiat_currency: *const u8, + fiat_currency_size: u32, + unchecked_extrinsic: *mut u8, + unchecked_extrinsic_size: u32, + ) -> sgx_status_t; + + pub fn run_state_provisioning_server( + eid: sgx_enclave_id_t, + retval: *mut sgx_status_t, + socket_fd: c_int, + sign_type: sgx_quote_sign_type_t, + skip_ra: c_int, + ) -> sgx_status_t; + + pub fn request_state_provisioning( + eid: sgx_enclave_id_t, + retval: *mut sgx_status_t, + socket_fd: c_int, + sign_type: sgx_quote_sign_type_t, + shard: *const u8, + shard_size: u32, + skip_ra: c_int, + ) -> sgx_status_t; + + pub fn run_stf_task_handler(eid: sgx_enclave_id_t, retval: *mut sgx_status_t) -> sgx_status_t; +} diff --git a/tee-worker/core-primitives/enclave-api/src/direct_request.rs b/tee-worker/core-primitives/enclave-api/src/direct_request.rs new file mode 100644 index 0000000000..570b27b484 --- /dev/null +++ b/tee-worker/core-primitives/enclave-api/src/direct_request.rs @@ -0,0 +1,52 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + Copyright (C) 2017-2019 Baidu, Inc. All Rights Reserved. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +use crate::{error::Error, Enclave, EnclaveResult}; +use frame_support::ensure; +use itp_enclave_api_ffi as ffi; +use sgx_types::sgx_status_t; + +pub trait DirectRequest: Send + Sync + 'static { + // Todo: Vec shall be replaced by D: Decode, E: Encode but this is currently + // not compatible with the direct_api_server... + fn rpc(&self, request: Vec) -> EnclaveResult>; +} + +impl DirectRequest for Enclave { + fn rpc(&self, request: Vec) -> EnclaveResult> { + let mut retval = sgx_status_t::SGX_SUCCESS; + let response_len = 8192; + let mut response: Vec = vec![0u8; response_len as usize]; + + let res = unsafe { + ffi::call_rpc_methods( + self.eid, + &mut retval, + request.as_ptr(), + request.len() as u32, + response.as_mut_ptr(), + response_len, + ) + }; + + ensure!(res == sgx_status_t::SGX_SUCCESS, Error::Sgx(res)); + ensure!(retval == sgx_status_t::SGX_SUCCESS, Error::Sgx(retval)); + + Ok(response) + } +} diff --git a/tee-worker/core-primitives/enclave-api/src/enclave_base.rs b/tee-worker/core-primitives/enclave-api/src/enclave_base.rs new file mode 100644 index 0000000000..389270ba4a --- /dev/null +++ b/tee-worker/core-primitives/enclave-api/src/enclave_base.rs @@ -0,0 +1,270 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + Copyright (C) 2017-2019 Baidu, Inc. All Rights Reserved. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +use crate::{error::Error, Enclave, EnclaveResult}; +use codec::{Decode, Encode}; +use core::fmt::Debug; +use frame_support::ensure; +use itc_parentchain::primitives::ParentchainInitParams; +use itp_enclave_api_ffi as ffi; +use itp_settings::worker::{ + HEADER_MAX_SIZE, MR_ENCLAVE_SIZE, SHIELDING_KEY_SIZE, SIGNING_KEY_SIZE, +}; +use log::*; +use sgx_crypto_helper::rsa3072::Rsa3072PubKey; +use sgx_types::*; +use sp_core::ed25519; + +/// Trait for base/common Enclave API functions +pub trait EnclaveBase: Send + Sync + 'static { + /// Initialize the enclave (needs to be called once at application startup). + fn init(&self, mu_ra_addr: &str, untrusted_worker_addr: &str) -> EnclaveResult<()>; + + /// Initialize the enclave sidechain components. + fn init_enclave_sidechain_components(&self) -> EnclaveResult<()>; + + /// Initialize the direct invocation RPC server. + fn init_direct_invocation_server(&self, rpc_server_addr: String) -> EnclaveResult<()>; + + /// Initialize the light client (needs to be called once at application startup). + fn init_parentchain_components( + &self, + params: ParentchainInitParams, + ) -> EnclaveResult
; + + /// Initialize a new shard. + fn init_shard(&self, shard: Vec) -> EnclaveResult<()>; + + /// Trigger the import of parentchain block explicitly. Used when initializing a light-client + /// with a triggered import dispatcher. + fn trigger_parentchain_block_import(&self) -> EnclaveResult<()>; + + fn set_nonce(&self, nonce: u32) -> EnclaveResult<()>; + + fn set_node_metadata(&self, metadata: Vec) -> EnclaveResult<()>; + + fn get_rsa_shielding_pubkey(&self) -> EnclaveResult; + + fn get_ecc_signing_pubkey(&self) -> EnclaveResult; + + fn get_mrenclave(&self) -> EnclaveResult<[u8; MR_ENCLAVE_SIZE]>; +} + +/// EnclaveApi implementation for Enclave struct +impl EnclaveBase for Enclave { + fn init(&self, mu_ra_addr: &str, untrusted_worker_addr: &str) -> EnclaveResult<()> { + let mut retval = sgx_status_t::SGX_SUCCESS; + + let encoded_mu_ra_addr = mu_ra_addr.encode(); + let encoded_untrusted_worker_addr = untrusted_worker_addr.encode(); + + let result = unsafe { + ffi::init( + self.eid, + &mut retval, + encoded_mu_ra_addr.as_ptr(), + encoded_mu_ra_addr.len() as u32, + encoded_untrusted_worker_addr.as_ptr(), + encoded_untrusted_worker_addr.len() as u32, + ) + }; + + ensure!(result == sgx_status_t::SGX_SUCCESS, Error::Sgx(result)); + ensure!(retval == sgx_status_t::SGX_SUCCESS, Error::Sgx(retval)); + + Ok(()) + } + + fn init_enclave_sidechain_components(&self) -> EnclaveResult<()> { + let mut retval = sgx_status_t::SGX_SUCCESS; + + let result = unsafe { ffi::init_enclave_sidechain_components(self.eid, &mut retval) }; + + ensure!(result == sgx_status_t::SGX_SUCCESS, Error::Sgx(result)); + ensure!(retval == sgx_status_t::SGX_SUCCESS, Error::Sgx(retval)); + + Ok(()) + } + + fn init_direct_invocation_server(&self, rpc_server_addr: String) -> EnclaveResult<()> { + let mut retval = sgx_status_t::SGX_SUCCESS; + + let encoded_rpc_server_addr = rpc_server_addr.encode(); + + let result = unsafe { + ffi::init_direct_invocation_server( + self.eid, + &mut retval, + encoded_rpc_server_addr.as_ptr(), + encoded_rpc_server_addr.len() as u32, + ) + }; + + ensure!(result == sgx_status_t::SGX_SUCCESS, Error::Sgx(result)); + ensure!(retval == sgx_status_t::SGX_SUCCESS, Error::Sgx(retval)); + + Ok(()) + } + + fn init_parentchain_components( + &self, + params: ParentchainInitParams, + ) -> EnclaveResult
{ + let latest_header_encoded = init_parentchain_components_ffi(self.eid, params.encode())?; + + let latest = Header::decode(&mut latest_header_encoded.as_slice())?; + info!("Latest Header {:?}", latest); + + Ok(latest) + } + + fn init_shard(&self, shard: Vec) -> EnclaveResult<()> { + let mut retval = sgx_status_t::SGX_SUCCESS; + + let result = + unsafe { ffi::init_shard(self.eid, &mut retval, shard.as_ptr(), shard.len() as u32) }; + + ensure!(result == sgx_status_t::SGX_SUCCESS, Error::Sgx(result)); + ensure!(retval == sgx_status_t::SGX_SUCCESS, Error::Sgx(retval)); + + Ok(()) + } + + fn trigger_parentchain_block_import(&self) -> EnclaveResult<()> { + let mut retval = sgx_status_t::SGX_SUCCESS; + + let result = unsafe { ffi::trigger_parentchain_block_import(self.eid, &mut retval) }; + + ensure!(result == sgx_status_t::SGX_SUCCESS, Error::Sgx(result)); + ensure!(retval == sgx_status_t::SGX_SUCCESS, Error::Sgx(retval)); + + Ok(()) + } + + fn set_nonce(&self, nonce: u32) -> EnclaveResult<()> { + let mut retval = sgx_status_t::SGX_SUCCESS; + + let result = unsafe { ffi::set_nonce(self.eid, &mut retval, &nonce) }; + + ensure!(result == sgx_status_t::SGX_SUCCESS, Error::Sgx(result)); + ensure!(retval == sgx_status_t::SGX_SUCCESS, Error::Sgx(retval)); + + Ok(()) + } + + fn set_node_metadata(&self, metadata: Vec) -> EnclaveResult<()> { + let mut retval = sgx_status_t::SGX_SUCCESS; + + let result = unsafe { + ffi::set_node_metadata(self.eid, &mut retval, metadata.as_ptr(), metadata.len() as u32) + }; + + ensure!(result == sgx_status_t::SGX_SUCCESS, Error::Sgx(result)); + ensure!(retval == sgx_status_t::SGX_SUCCESS, Error::Sgx(retval)); + + Ok(()) + } + + fn get_rsa_shielding_pubkey(&self) -> EnclaveResult { + let mut retval = sgx_status_t::SGX_SUCCESS; + + let pubkey_size = SHIELDING_KEY_SIZE; + let mut pubkey = vec![0u8; pubkey_size]; + + let result = unsafe { + ffi::get_rsa_encryption_pubkey( + self.eid, + &mut retval, + pubkey.as_mut_ptr(), + pubkey.len() as u32, + ) + }; + + ensure!(result == sgx_status_t::SGX_SUCCESS, Error::Sgx(result)); + ensure!(retval == sgx_status_t::SGX_SUCCESS, Error::Sgx(retval)); + + let rsa_pubkey: Rsa3072PubKey = + serde_json::from_slice(pubkey.as_slice()).expect("Invalid public key"); + debug!("got RSA pubkey {:?}", rsa_pubkey); + Ok(rsa_pubkey) + } + + fn get_ecc_signing_pubkey(&self) -> EnclaveResult { + let mut retval = sgx_status_t::SGX_SUCCESS; + let mut pubkey = [0u8; SIGNING_KEY_SIZE]; + + let result = unsafe { + ffi::get_ecc_signing_pubkey( + self.eid, + &mut retval, + pubkey.as_mut_ptr(), + pubkey.len() as u32, + ) + }; + + ensure!(result == sgx_status_t::SGX_SUCCESS, Error::Sgx(result)); + ensure!(retval == sgx_status_t::SGX_SUCCESS, Error::Sgx(retval)); + + Ok(ed25519::Public::from_raw(pubkey)) + } + + fn get_mrenclave(&self) -> EnclaveResult<[u8; MR_ENCLAVE_SIZE]> { + let mut retval = sgx_status_t::SGX_SUCCESS; + let mut mr_enclave = [0u8; MR_ENCLAVE_SIZE]; + + let result = unsafe { + ffi::get_mrenclave( + self.eid, + &mut retval, + mr_enclave.as_mut_ptr(), + mr_enclave.len() as u32, + ) + }; + + ensure!(result == sgx_status_t::SGX_SUCCESS, Error::Sgx(result)); + ensure!(retval == sgx_status_t::SGX_SUCCESS, Error::Sgx(retval)); + + Ok(mr_enclave) + } +} + +fn init_parentchain_components_ffi( + enclave_id: sgx_enclave_id_t, + params: Vec, +) -> EnclaveResult> { + let mut retval = sgx_status_t::SGX_SUCCESS; + + let latest_header_size = HEADER_MAX_SIZE; + let mut latest_header = vec![0u8; latest_header_size]; + + let result = unsafe { + ffi::init_parentchain_components( + enclave_id, + &mut retval, + params.as_ptr(), + params.len(), + latest_header.as_mut_ptr(), + latest_header.len(), + ) + }; + + ensure!(result == sgx_status_t::SGX_SUCCESS, Error::Sgx(result)); + ensure!(retval == sgx_status_t::SGX_SUCCESS, Error::Sgx(retval)); + + Ok(latest_header) +} diff --git a/tee-worker/core-primitives/enclave-api/src/enclave_test.rs b/tee-worker/core-primitives/enclave-api/src/enclave_test.rs new file mode 100644 index 0000000000..6bdf85c789 --- /dev/null +++ b/tee-worker/core-primitives/enclave-api/src/enclave_test.rs @@ -0,0 +1,42 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + Copyright (C) 2017-2019 Baidu, Inc. All Rights Reserved. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +use crate::{error::Error, Enclave, EnclaveResult}; +use frame_support::ensure; +use itp_enclave_api_ffi as ffi; +use log::*; +use sgx_types::sgx_status_t; + +pub trait EnclaveTest: Send + Sync + 'static { + fn test_main_entrance(&self) -> EnclaveResult<()>; +} + +impl EnclaveTest for Enclave { + fn test_main_entrance(&self) -> EnclaveResult<()> { + let mut retval = sgx_status_t::SGX_SUCCESS; + + let result = unsafe { ffi::test_main_entrance(self.eid, &mut retval) }; + + ensure!(result == sgx_status_t::SGX_SUCCESS, Error::Sgx(result)); + ensure!(retval == sgx_status_t::SGX_SUCCESS, Error::Sgx(retval)); + + debug!("[+] successfully executed enclave test main"); + + Ok(()) + } +} diff --git a/tee-worker/core-primitives/enclave-api/src/error.rs b/tee-worker/core-primitives/enclave-api/src/error.rs new file mode 100644 index 0000000000..c29d4518fb --- /dev/null +++ b/tee-worker/core-primitives/enclave-api/src/error.rs @@ -0,0 +1,12 @@ +use codec::Error as CodecError; +use sgx_types::sgx_status_t; + +#[derive(Debug, thiserror::Error)] +pub enum Error { + #[error("{0}")] + Codec(#[from] CodecError), + #[error("Enclave Error: {0}")] + Sgx(sgx_status_t), + #[error("Error, other: {0}")] + Other(Box), +} diff --git a/tee-worker/core-primitives/enclave-api/src/lib.rs b/tee-worker/core-primitives/enclave-api/src/lib.rs new file mode 100644 index 0000000000..d510cb3a52 --- /dev/null +++ b/tee-worker/core-primitives/enclave-api/src/lib.rs @@ -0,0 +1,45 @@ +//! Some definitions and traits that facilitate interaction with the enclave. +//! +//! This serves as a proof of concept on how we could design the interface between the worker and +//! the enclave. +//! +//! Design principle here should be to keep the traits as slim as possible - because then the +//! worker can also define slim interfaces with less demanding trait bounds. +//! +//! This can further be simplified once https://github.com/integritee-network/worker/issues/254 +//! is implemented. Then we can replace the several ffi:: and the boilerplate code +//! around it with a simple `fn ecall(call: CallEnum) -> Result`, which wraps one single +//! ffi function. +//! + +use crate::error::Error; +use sgx_types::*; +use sgx_urts::SgxEnclave; + +pub mod direct_request; +pub mod enclave_base; +pub mod enclave_test; +pub mod error; +pub mod remote_attestation; +pub mod sidechain; +pub mod stf_task_handler; +pub mod teeracle_api; +pub mod utils; + +pub type EnclaveResult = Result; + +#[derive(Clone, Debug, Default)] +pub struct Enclave { + eid: sgx_enclave_id_t, + sgx_enclave: SgxEnclave, +} + +impl Enclave { + pub fn new(sgx_enclave: SgxEnclave) -> Self { + Enclave { eid: sgx_enclave.geteid(), sgx_enclave } + } + + pub fn destroy(self) { + self.sgx_enclave.destroy() + } +} diff --git a/tee-worker/core-primitives/enclave-api/src/remote_attestation.rs b/tee-worker/core-primitives/enclave-api/src/remote_attestation.rs new file mode 100644 index 0000000000..b69a4631fb --- /dev/null +++ b/tee-worker/core-primitives/enclave-api/src/remote_attestation.rs @@ -0,0 +1,254 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + Copyright (C) 2017-2019 Baidu, Inc. All Rights Reserved. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +use crate::{error::Error, utils, Enclave, EnclaveResult}; +use codec::Encode; +use frame_support::ensure; +use itp_enclave_api_ffi as ffi; +use itp_settings::worker::EXTRINSIC_MAX_SIZE; +use itp_types::ShardIdentifier; +use sgx_types::*; + +/// general remote attestation methods +pub trait RemoteAttestation { + fn perform_ra(&self, w_url: &str, skip_ra: bool) -> EnclaveResult>; + + fn dump_ra_to_disk(&self) -> EnclaveResult<()>; +} + +/// call-backs that are made from inside the enclave (using o-call), to e-calls again inside the enclave +pub trait RemoteAttestationCallBacks { + fn init_quote(&self) -> EnclaveResult<(sgx_target_info_t, sgx_epid_group_id_t)>; + + fn calc_quote_size(&self, revocation_list: Vec) -> EnclaveResult; + + fn get_quote( + &self, + revocation_list: Vec, + report: sgx_report_t, + quote_type: sgx_quote_sign_type_t, + spid: sgx_spid_t, + quote_nonce: sgx_quote_nonce_t, + quote_length: u32, + ) -> EnclaveResult<(sgx_report_t, Vec)>; + + fn get_update_info( + &self, + platform_blob: sgx_platform_info_t, + enclave_trusted: i32, + ) -> EnclaveResult; +} + +/// TLS remote attestations methods +pub trait TlsRemoteAttestation { + fn run_state_provisioning_server( + &self, + socket_fd: c_int, + sign_type: sgx_quote_sign_type_t, + skip_ra: bool, + ) -> EnclaveResult<()>; + + fn request_state_provisioning( + &self, + socket_fd: c_int, + sign_type: sgx_quote_sign_type_t, + shard: &ShardIdentifier, + skip_ra: bool, + ) -> EnclaveResult<()>; +} + +impl RemoteAttestation for Enclave { + fn perform_ra(&self, w_url: &str, skip_ra: bool) -> EnclaveResult> { + let mut retval = sgx_status_t::SGX_SUCCESS; + + let unchecked_extrinsic_size = EXTRINSIC_MAX_SIZE; + let mut unchecked_extrinsic: Vec = vec![0u8; unchecked_extrinsic_size]; + + let url = w_url.encode(); + + let result = unsafe { + ffi::perform_ra( + self.eid, + &mut retval, + url.as_ptr(), + url.len() as u32, + unchecked_extrinsic.as_mut_ptr(), + unchecked_extrinsic.len() as u32, + skip_ra.into(), + ) + }; + + ensure!(result == sgx_status_t::SGX_SUCCESS, Error::Sgx(result)); + ensure!(retval == sgx_status_t::SGX_SUCCESS, Error::Sgx(retval)); + + Ok(unchecked_extrinsic) + } + + fn dump_ra_to_disk(&self) -> EnclaveResult<()> { + let mut retval = sgx_status_t::SGX_SUCCESS; + + let result = unsafe { ffi::dump_ra_to_disk(self.eid, &mut retval) }; + + ensure!(result == sgx_status_t::SGX_SUCCESS, Error::Sgx(result)); + ensure!(retval == sgx_status_t::SGX_SUCCESS, Error::Sgx(retval)); + + Ok(()) + } +} + +impl RemoteAttestationCallBacks for Enclave { + fn init_quote(&self) -> EnclaveResult<(sgx_target_info_t, sgx_epid_group_id_t)> { + let mut ti: sgx_target_info_t = sgx_target_info_t::default(); + let mut eg: sgx_epid_group_id_t = sgx_epid_group_id_t::default(); + + let result = unsafe { + sgx_init_quote(&mut ti as *mut sgx_target_info_t, &mut eg as *mut sgx_epid_group_id_t) + }; + + ensure!(result == sgx_status_t::SGX_SUCCESS, Error::Sgx(result)); + + Ok((ti, eg)) + } + + fn calc_quote_size(&self, revocation_list: Vec) -> EnclaveResult { + let mut real_quote_len: u32 = 0; + + let (p_sig_rl, sig_rl_size) = utils::vec_to_c_pointer_with_len(revocation_list); + + let result = + unsafe { sgx_calc_quote_size(p_sig_rl, sig_rl_size, &mut real_quote_len as *mut u32) }; + + ensure!(result == sgx_status_t::SGX_SUCCESS, Error::Sgx(result)); + + Ok(real_quote_len) + } + + fn get_quote( + &self, + revocation_list: Vec, + report: sgx_report_t, + quote_type: sgx_quote_sign_type_t, + spid: sgx_spid_t, + quote_nonce: sgx_quote_nonce_t, + quote_length: u32, + ) -> EnclaveResult<(sgx_report_t, Vec)> { + let (p_sig_rl, sig_rl_size) = utils::vec_to_c_pointer_with_len(revocation_list); + let p_report = &report as *const sgx_report_t; + let p_spid = &spid as *const sgx_spid_t; + let p_nonce = "e_nonce as *const sgx_quote_nonce_t; + + let mut qe_report = sgx_report_t::default(); + let p_qe_report = &mut qe_report as *mut sgx_report_t; + + let mut return_quote_buf = vec![0u8; quote_length as usize]; + let p_quote = return_quote_buf.as_mut_ptr(); + + let ret = unsafe { + sgx_get_quote( + p_report, + quote_type, + p_spid, + p_nonce, + p_sig_rl, + sig_rl_size, + p_qe_report, + p_quote as *mut sgx_quote_t, + quote_length, + ) + }; + + ensure!(ret == sgx_status_t::SGX_SUCCESS, Error::Sgx(ret)); + + Ok((qe_report, return_quote_buf)) + } + + fn get_update_info( + &self, + platform_blob: sgx_platform_info_t, + enclave_trusted: i32, + ) -> EnclaveResult { + let mut update_info: sgx_update_info_bit_t = sgx_update_info_bit_t::default(); + + let result = unsafe { + sgx_report_attestation_status( + &platform_blob as *const sgx_platform_info_t, + enclave_trusted, + &mut update_info as *mut sgx_update_info_bit_t, + ) + }; + + ensure!(result == sgx_status_t::SGX_SUCCESS, Error::Sgx(result)); + + Ok(update_info) + } +} + +impl TlsRemoteAttestation for Enclave { + fn run_state_provisioning_server( + &self, + socket_fd: c_int, + sign_type: sgx_quote_sign_type_t, + skip_ra: bool, + ) -> EnclaveResult<()> { + let mut retval = sgx_status_t::SGX_SUCCESS; + + let result = unsafe { + ffi::run_state_provisioning_server( + self.eid, + &mut retval, + socket_fd, + sign_type, + skip_ra.into(), + ) + }; + + ensure!(result == sgx_status_t::SGX_SUCCESS, Error::Sgx(result)); + ensure!(retval == sgx_status_t::SGX_SUCCESS, Error::Sgx(retval)); + + Ok(()) + } + + fn request_state_provisioning( + &self, + socket_fd: c_int, + sign_type: sgx_quote_sign_type_t, + shard: &ShardIdentifier, + skip_ra: bool, + ) -> EnclaveResult<()> { + let mut retval = sgx_status_t::SGX_SUCCESS; + + let encoded_shard = shard.encode(); + + let result = unsafe { + ffi::request_state_provisioning( + self.eid, + &mut retval, + socket_fd, + sign_type, + encoded_shard.as_ptr(), + encoded_shard.len() as u32, + skip_ra.into(), + ) + }; + + ensure!(result == sgx_status_t::SGX_SUCCESS, Error::Sgx(result)); + ensure!(retval == sgx_status_t::SGX_SUCCESS, Error::Sgx(retval)); + + Ok(()) + } +} diff --git a/tee-worker/core-primitives/enclave-api/src/sidechain.rs b/tee-worker/core-primitives/enclave-api/src/sidechain.rs new file mode 100644 index 0000000000..0b045839d3 --- /dev/null +++ b/tee-worker/core-primitives/enclave-api/src/sidechain.rs @@ -0,0 +1,73 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + Copyright (C) 2017-2019 Baidu, Inc. All Rights Reserved. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +use crate::{error::Error, Enclave, EnclaveResult}; +use codec::Encode; +use frame_support::ensure; +use itp_enclave_api_ffi as ffi; +use sgx_types::sgx_status_t; +use sp_runtime::{generic::SignedBlock, traits::Block as ParentchainBlockTrait}; + +/// trait for handling blocks on the side chain +pub trait Sidechain: Send + Sync + 'static { + /// Sync parentchain blocks and execute pending tops in the enclave + fn sync_parentchain( + &self, + blocks: &[SignedBlock], + nonce: u32, + ) -> EnclaveResult<()>; + + fn execute_trusted_calls(&self) -> EnclaveResult<()>; +} + +impl Sidechain for Enclave { + fn sync_parentchain( + &self, + blocks: &[SignedBlock], + nonce: u32, + ) -> EnclaveResult<()> { + let mut retval = sgx_status_t::SGX_SUCCESS; + let blocks_enc = blocks.encode(); + + let result = unsafe { + ffi::sync_parentchain( + self.eid, + &mut retval, + blocks_enc.as_ptr(), + blocks_enc.len(), + &nonce, + ) + }; + + ensure!(result == sgx_status_t::SGX_SUCCESS, Error::Sgx(result)); + ensure!(retval == sgx_status_t::SGX_SUCCESS, Error::Sgx(retval)); + + Ok(()) + } + + fn execute_trusted_calls(&self) -> EnclaveResult<()> { + let mut retval = sgx_status_t::SGX_SUCCESS; + + let result = unsafe { ffi::execute_trusted_calls(self.eid, &mut retval) }; + + ensure!(result == sgx_status_t::SGX_SUCCESS, Error::Sgx(result)); + ensure!(retval == sgx_status_t::SGX_SUCCESS, Error::Sgx(retval)); + + Ok(()) + } +} diff --git a/tee-worker/core-primitives/enclave-api/src/stf_task_handler.rs b/tee-worker/core-primitives/enclave-api/src/stf_task_handler.rs new file mode 100644 index 0000000000..94d8438baf --- /dev/null +++ b/tee-worker/core-primitives/enclave-api/src/stf_task_handler.rs @@ -0,0 +1,38 @@ +// Copyright 2020-2022 Litentry Technologies GmbH. +// This file is part of Litentry. +// +// Litentry is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Litentry is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Litentry. If not, see . + +use crate::{error::Error, Enclave, EnclaveResult}; +use frame_support::ensure; +use itp_enclave_api_ffi as ffi; +use sgx_types::*; + +/// Trait to run a stf task handling thread inside the enclave. +pub trait StfTaskHandler { + fn run_stf_task_handler(&self) -> EnclaveResult<()>; +} + +impl StfTaskHandler for Enclave { + fn run_stf_task_handler(&self) -> EnclaveResult<()> { + let mut retval = sgx_status_t::SGX_SUCCESS; + + let result = unsafe { ffi::run_stf_task_handler(self.eid, &mut retval) }; + + ensure!(result == sgx_status_t::SGX_SUCCESS, Error::Sgx(result)); + ensure!(retval == sgx_status_t::SGX_SUCCESS, Error::Sgx(retval)); + + Ok(()) + } +} diff --git a/tee-worker/core-primitives/enclave-api/src/teeracle_api.rs b/tee-worker/core-primitives/enclave-api/src/teeracle_api.rs new file mode 100644 index 0000000000..b4e7159264 --- /dev/null +++ b/tee-worker/core-primitives/enclave-api/src/teeracle_api.rs @@ -0,0 +1,69 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +use crate::{error::Error, Enclave, EnclaveResult}; +use codec::Encode; +use frame_support::ensure; +use itp_enclave_api_ffi as ffi; +use log::*; +use sgx_types::*; + +pub trait TeeracleApi: Send + Sync + 'static { + /// update the currency market data for the token oracle. + fn update_market_data_xt( + &self, + crypto_currency: &str, + fiat_currency: &str, + ) -> EnclaveResult>; +} + +impl TeeracleApi for Enclave { + fn update_market_data_xt( + &self, + crypto_currency: &str, + fiat_currency: &str, + ) -> EnclaveResult> { + info!( + "TeeracleApi update_market_data_xt in with crypto {} and fiat {}", + crypto_currency, fiat_currency + ); + let mut retval = sgx_status_t::SGX_SUCCESS; + let response_len = 8192; + let mut response: Vec = vec![0u8; response_len as usize]; + + let crypto_curr = crypto_currency.encode(); + let fiat_curr = fiat_currency.encode(); + + let res = unsafe { + ffi::update_market_data_xt( + self.eid, + &mut retval, + crypto_curr.as_ptr(), + crypto_curr.len() as u32, + fiat_curr.as_ptr(), + fiat_curr.len() as u32, + response.as_mut_ptr(), + response_len, + ) + }; + + ensure!(res == sgx_status_t::SGX_SUCCESS, Error::Sgx(res)); + ensure!(retval == sgx_status_t::SGX_SUCCESS, Error::Sgx(retval)); + + Ok(response) + } +} diff --git a/tee-worker/core-primitives/enclave-api/src/utils.rs b/tee-worker/core-primitives/enclave-api/src/utils.rs new file mode 100644 index 0000000000..e36764f7ac --- /dev/null +++ b/tee-worker/core-primitives/enclave-api/src/utils.rs @@ -0,0 +1,27 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + Copyright (C) 2017-2019 Baidu, Inc. All Rights Reserved. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +use std::ptr; + +pub fn vec_to_c_pointer_with_len(input: Vec) -> (*const A, u32) { + if input.is_empty() { + (ptr::null(), 0) + } else { + (input.as_ptr(), input.len() as u32) + } +} diff --git a/tee-worker/core-primitives/enclave-metrics/Cargo.toml b/tee-worker/core-primitives/enclave-metrics/Cargo.toml new file mode 100644 index 0000000000..43d87ea998 --- /dev/null +++ b/tee-worker/core-primitives/enclave-metrics/Cargo.toml @@ -0,0 +1,25 @@ +[package] +authors = ["Integritee AG "] +edition = "2021" +name = "itp-enclave-metrics" +version = "0.9.0" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +# sgx +sgx_tstd = { branch = "master", git = "https://github.com/apache/teaclave-sgx-sdk.git", optional = true } + +# no-std dependencies +codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive", "full"] } +substrate-fixed = { default-features = false, git = "https://github.com/encointer/substrate-fixed", tag = "v0.5.9" } + +[features] +default = ["std"] +sgx = [ + "sgx_tstd", +] +std = [ + "substrate-fixed/std", + "codec/std", +] diff --git a/tee-worker/core-primitives/enclave-metrics/src/lib.rs b/tee-worker/core-primitives/enclave-metrics/src/lib.rs new file mode 100644 index 0000000000..875637ab69 --- /dev/null +++ b/tee-worker/core-primitives/enclave-metrics/src/lib.rs @@ -0,0 +1,50 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +#![cfg_attr(not(feature = "std"), no_std)] + +#[cfg(all(feature = "std", feature = "sgx"))] +compile_error!("feature \"std\" and feature \"sgx\" cannot be enabled at the same time"); + +#[cfg(all(not(feature = "std"), feature = "sgx"))] +extern crate sgx_tstd as std; + +use codec::{Decode, Encode}; +use std::string::String; +use substrate_fixed::types::U32F32; + +// FIXME: Copied from ita-exchange-oracle because of cyclic deps. Should be removed after integritee-network/pallets#71 +pub type ExchangeRate = U32F32; + +#[derive(Encode, Decode, Debug)] +pub enum EnclaveMetric { + SetSidechainBlockHeight(u64), + TopPoolSizeSet(u64), + TopPoolSizeIncrement, + TopPoolSizeDecrement, + ExchangeRateOracle(ExchangeRateOracleMetric), +} + +#[derive(Encode, Decode, Debug)] +pub enum ExchangeRateOracleMetric { + /// Exchange Rate from CoinGecko - (Source, TradingPair, ExchangeRate) + ExchangeRate(String, String, ExchangeRate), + /// Response time of the request in [ms]. (Source, ResponseTime) + ResponseTime(String, u128), + /// Increment the number of requests (Source) + NumberRequestsIncrement(String), +} diff --git a/tee-worker/core-primitives/extrinsics-factory/Cargo.toml b/tee-worker/core-primitives/extrinsics-factory/Cargo.toml new file mode 100644 index 0000000000..042ff42473 --- /dev/null +++ b/tee-worker/core-primitives/extrinsics-factory/Cargo.toml @@ -0,0 +1,46 @@ +[package] +authors = ["Integritee AG "] +edition = "2021" +name = "itp-extrinsics-factory" +version = "0.9.0" + +[dependencies] +# sgx dependencies +sgx_tstd = { branch = "master", git = "https://github.com/apache/teaclave-sgx-sdk.git", optional = true } +sgx_types = { branch = "master", git = "https://github.com/apache/teaclave-sgx-sdk.git" } +substrate-api-client = { default-features = false, git = "https://github.com/scs/substrate-api-client.git", branch = "polkadot-v0.9.29" } + +# local dependencies +itp-node-api = { path = "../node-api", default-features = false } +itp-nonce-cache = { path = "../nonce-cache", default-features = false } +itp-types = { path = "../types", default-features = false } + +# sgx enabled external libraries +thiserror_sgx = { package = "thiserror", git = "https://github.com/mesalock-linux/thiserror-sgx", tag = "sgx_1.1.3", optional = true } + +# std compatible external libraries (make sure these versions match with the sgx-enabled ones above) +thiserror = { version = "1.0", optional = true } + +# no-std dependencies +codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } +log = { version = "0.4", default-features = false } +sp-core = { default-features = false, features = ["full_crypto"], git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.29" } +sp-runtime = { default-features = false, git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.29" } + +[features] +default = ["std"] +mocks = [] +sgx = [ + "itp-node-api/sgx", + "itp-nonce-cache/sgx", + "sgx_tstd", + "thiserror_sgx", +] +std = [ + "itp-node-api/std", + "itp-nonce-cache/std", + "itp-types/std", + "log/std", + "substrate-api-client/std", + "thiserror", +] diff --git a/tee-worker/core-primitives/extrinsics-factory/src/error.rs b/tee-worker/core-primitives/extrinsics-factory/src/error.rs new file mode 100644 index 0000000000..4f052b9f94 --- /dev/null +++ b/tee-worker/core-primitives/extrinsics-factory/src/error.rs @@ -0,0 +1,49 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +#[cfg(all(not(feature = "std"), feature = "sgx"))] +use crate::sgx_reexport_prelude::*; + +use sgx_types::sgx_status_t; +use std::{boxed::Box, format}; + +pub type Result = core::result::Result; + +/// extrinsics factory error +#[derive(Debug, thiserror::Error)] +pub enum Error { + #[error("Nonce cache error: {0}")] + NonceCache(#[from] itp_nonce_cache::error::Error), + #[error("Node API error: {0:?}")] + NodeMetadataProvider(#[from] itp_node_api::metadata::provider::Error), + #[error("SGX error, status: {0}")] + Sgx(sgx_status_t), + #[error(transparent)] + Other(#[from] Box), +} + +impl From for Error { + fn from(sgx_status: sgx_status_t) -> Self { + Self::Sgx(sgx_status) + } +} + +impl From for Error { + fn from(e: codec::Error) -> Self { + Self::Other(format!("{:?}", e).into()) + } +} diff --git a/tee-worker/core-primitives/extrinsics-factory/src/lib.rs b/tee-worker/core-primitives/extrinsics-factory/src/lib.rs new file mode 100644 index 0000000000..9e7bf393cf --- /dev/null +++ b/tee-worker/core-primitives/extrinsics-factory/src/lib.rs @@ -0,0 +1,201 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +#![cfg_attr(not(feature = "std"), no_std)] + +#[cfg(all(feature = "std", feature = "sgx"))] +compile_error!("feature \"std\" and feature \"sgx\" cannot be enabled at the same time"); + +#[cfg(all(not(feature = "std"), feature = "sgx"))] +extern crate sgx_tstd as std; + +// re-export module to properly feature gate sgx and regular std environment +#[cfg(all(not(feature = "std"), feature = "sgx"))] +pub mod sgx_reexport_prelude { + pub use thiserror_sgx as thiserror; +} + +use codec::Encode; +use error::Result; +use itp_node_api::{ + api_client::{ParentchainExtrinsicParams, ParentchainExtrinsicParamsBuilder}, + metadata::{provider::AccessNodeMetadata, NodeMetadata}, +}; +use itp_nonce_cache::{MutateNonce, Nonce}; +use itp_types::OpaqueCall; +use sp_core::{Pair, H256}; +use sp_runtime::{generic::Era, MultiSignature, OpaqueExtrinsic}; +use std::{sync::Arc, vec::Vec}; +use substrate_api_client::{compose_extrinsic_offline, ExtrinsicParams}; + +pub mod error; + +#[cfg(feature = "mocks")] +pub mod mock; + +/// Create extrinsics from opaque calls +/// +/// Also increases the nonce counter for each extrinsic that is created. +pub trait CreateExtrinsics { + fn create_extrinsics( + &self, + calls: &[OpaqueCall], + extrinsics_params_builder: Option, + ) -> Result>; +} + +/// Extrinsics factory +pub struct ExtrinsicsFactory +where + Signer: Pair, + Signer::Signature: Into, + NonceCache: MutateNonce, + NodeMetadataRepository: AccessNodeMetadata, +{ + genesis_hash: H256, + signer: Signer, + nonce_cache: Arc, + node_metadata_repository: Arc, +} + +impl + ExtrinsicsFactory +where + Signer: Pair, + Signer::Signature: Into, + NonceCache: MutateNonce, + NodeMetadataRepository: AccessNodeMetadata, +{ + pub fn new( + genesis_hash: H256, + signer: Signer, + nonce_cache: Arc, + node_metadata_repository: Arc, + ) -> Self { + ExtrinsicsFactory { genesis_hash, signer, nonce_cache, node_metadata_repository } + } +} + +impl CreateExtrinsics + for ExtrinsicsFactory +where + Signer: Pair, + Signer::Signature: Into, + NonceCache: MutateNonce, + NodeMetadataRepository: AccessNodeMetadata, +{ + fn create_extrinsics( + &self, + calls: &[OpaqueCall], + extrinsics_params_builder: Option, + ) -> Result> { + let mut nonce_lock = self.nonce_cache.load_for_mutation()?; + let mut nonce_value = nonce_lock.0; + + let params_builder = extrinsics_params_builder.unwrap_or_else(|| { + ParentchainExtrinsicParamsBuilder::new() + .era(Era::Immortal, self.genesis_hash) + .tip(0) + }); + + let (runtime_spec_version, runtime_transaction_version) = + self.node_metadata_repository.get_from_metadata(|m| { + (m.get_runtime_version(), m.get_runtime_transaction_version()) + })?; + + let extrinsics_buffer: Vec = calls + .iter() + .map(|call| { + let extrinsic_params = ParentchainExtrinsicParams::new( + runtime_spec_version, + runtime_transaction_version, + nonce_value, + self.genesis_hash, + params_builder, + ); + let xt = compose_extrinsic_offline!(self.signer.clone(), call, extrinsic_params) + .encode(); + nonce_value += 1; + xt + }) + .map(|xt| { + OpaqueExtrinsic::from_bytes(&xt) + .expect("A previously encoded extrinsic has valid codec; qed.") + }) + .collect(); + + *nonce_lock = Nonce(nonce_value); + + Ok(extrinsics_buffer) + } +} + +#[cfg(test)] +pub mod tests { + + use super::*; + use itp_node_api::metadata::provider::NodeMetadataRepository; + use itp_nonce_cache::{GetNonce, Nonce, NonceCache, NonceValue}; + use sp_core::ed25519; + //use substrate_api_client::extrinsic::xt_primitives::UncheckedExtrinsicV4; + + #[test] + pub fn creating_xts_increases_nonce_for_each_xt() { + let nonce_cache = Arc::new(NonceCache::default()); + let node_metadata_repo = Arc::new(NodeMetadataRepository::new(NodeMetadata::default())); + let extrinsics_factory = ExtrinsicsFactory::new( + test_genesis_hash(), + test_account(), + nonce_cache.clone(), + node_metadata_repo, + ); + + let opaque_calls = [OpaqueCall(vec![3u8; 42]), OpaqueCall(vec![12u8, 78])]; + let xts = extrinsics_factory.create_extrinsics(&opaque_calls, None).unwrap(); + + assert_eq!(opaque_calls.len(), xts.len()); + assert_eq!(nonce_cache.get_nonce().unwrap(), Nonce(opaque_calls.len() as NonceValue)); + } + + // #[test] + // pub fn xts_have_increasing_nonce() { + // let nonce_cache = Arc::new(NonceCache::default()); + // nonce_cache.set_nonce(Nonce(34)).unwrap(); + // let extrinsics_factory = + // ExtrinsicsFactory::new(test_genesis_hash(), test_account(), nonce_cache); + // + // let opaque_calls = + // [OpaqueCall(vec![3u8; 42]), OpaqueCall(vec![12u8, 78]), OpaqueCall(vec![15u8, 12])]; + // let xts: Vec> = extrinsics_factory + // .create_extrinsics(&opaque_calls) + // .unwrap() + // .iter() + // .map(|mut x| UncheckedExtrinsicV4::::decode(&mut x)) + // .collect(); + // + // assert_eq!(xts.len(), opaque_calls.len()); + // assert_eq!(xts[0].signature.unwrap().2 .2, 34u128); + // } + + fn test_account() -> ed25519::Pair { + ed25519::Pair::from_seed(b"42315678901234567890123456789012") + } + + fn test_genesis_hash() -> H256 { + H256::from_slice(&[56u8; 32]) + } +} diff --git a/tee-worker/core-primitives/extrinsics-factory/src/mock.rs b/tee-worker/core-primitives/extrinsics-factory/src/mock.rs new file mode 100644 index 0000000000..2f270f545e --- /dev/null +++ b/tee-worker/core-primitives/extrinsics-factory/src/mock.rs @@ -0,0 +1,46 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +use crate::{error::Result, CreateExtrinsics}; +use itp_node_api::api_client::ParentchainExtrinsicParamsBuilder; +use itp_types::OpaqueCall; +use sp_runtime::OpaqueExtrinsic; +use std::vec::Vec; + +/// Mock of an extrinsics factory. To be used in unit tests. +/// +/// Returns an empty extrinsic. +#[derive(Default, Clone)] +pub struct ExtrinsicsFactoryMock; + +impl CreateExtrinsics for ExtrinsicsFactoryMock { + fn create_extrinsics( + &self, + _calls: &[OpaqueCall], + _extrinsics_params_builder: Option, + ) -> Result> { + // Intention was to map an OpaqueCall to some dummy OpaqueExtrinsic, + // so the output vector has the same size as the input one (and thus can be tested from the outside). + // However, it doesn't seem to be possible to construct an empty of dummy OpaqueExtrinsic, + // `from_bytes` expects a valid encoded OpaqueExtrinsic. + // Ok(calls + // .iter() + // .map(|_| OpaqueExtrinsic::from_bytes(Vec::new().as_slice()).unwrap()) + // .collect()) + Ok(Vec::new()) + } +} diff --git a/tee-worker/core-primitives/hashing/Cargo.toml b/tee-worker/core-primitives/hashing/Cargo.toml new file mode 100644 index 0000000000..764fe03371 --- /dev/null +++ b/tee-worker/core-primitives/hashing/Cargo.toml @@ -0,0 +1,13 @@ +[package] +authors = ["Integritee AG "] +edition = "2021" +name = "itp-hashing" +version = "0.9.0" + +[dependencies] +# substrate +sp-core = { default-features = false, git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.29" } + +[features] +default = ["std"] +std = [] diff --git a/tee-worker/core-primitives/hashing/src/lib.rs b/tee-worker/core-primitives/hashing/src/lib.rs new file mode 100644 index 0000000000..6e44afbcaa --- /dev/null +++ b/tee-worker/core-primitives/hashing/src/lib.rs @@ -0,0 +1,46 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ +//! Hashing traits and utilities. + +#![cfg_attr(not(feature = "std"), no_std)] + +use sp_core::H256; + +#[cfg(feature = "std")] +pub mod std_hash; + +/// Trait to compute a hash of self. +pub trait Hash { + fn hash(&self) -> Output; +} + +// Cannot use the implementation below unfortunately, because our externalities +// have their own hash implementation which ignores the state diff. +// /// Implement Hash for any types that implement encode. +// /// +// /// +// impl Hash for T { +// fn hash(&self) -> H256 { +// blake2_256(&self.encode()).into() +// } +// } + +pub fn hash_from_slice(hash_slize: &[u8]) -> H256 { + let mut g = [0; 32]; + g.copy_from_slice(hash_slize); + H256::from(&mut g) +} diff --git a/tee-worker/core-primitives/hashing/src/std_hash.rs b/tee-worker/core-primitives/hashing/src/std_hash.rs new file mode 100644 index 0000000000..2a6524a800 --- /dev/null +++ b/tee-worker/core-primitives/hashing/src/std_hash.rs @@ -0,0 +1,31 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +use crate::Hash; +use std::{ + collections::hash_map::DefaultHasher, + hash::{Hash as StdHash, Hasher}, +}; + +/// Implement Hash for all types implementing core::hash::Hash. +impl Hash for T { + fn hash(&self) -> u64 { + let mut hasher = DefaultHasher::new(); + self.hash(&mut hasher); + hasher.finish() + } +} diff --git a/tee-worker/core-primitives/networking-utils/Cargo.toml b/tee-worker/core-primitives/networking-utils/Cargo.toml new file mode 100644 index 0000000000..c9863e373d --- /dev/null +++ b/tee-worker/core-primitives/networking-utils/Cargo.toml @@ -0,0 +1,20 @@ +[package] +authors = ["Integritee AG "] +edition = "2021" +name = "itp-networking-utils" +version = "0.9.0" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +# sgx dependencies +sgx_tstd = { branch = "master", git = "https://github.com/apache/teaclave-sgx-sdk.git", optional = true } + +[features] +default = ["std"] +sgx = [ + "sgx_tstd", +] +std = [ + +] diff --git a/tee-worker/core-primitives/networking-utils/src/lib.rs b/tee-worker/core-primitives/networking-utils/src/lib.rs new file mode 100644 index 0000000000..46b8ab91d3 --- /dev/null +++ b/tee-worker/core-primitives/networking-utils/src/lib.rs @@ -0,0 +1,26 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +#![cfg_attr(not(feature = "std"), no_std)] + +#[cfg(all(feature = "std", feature = "sgx"))] +compile_error!("feature \"std\" and feature \"sgx\" cannot be enabled at the same time"); + +#[cfg(all(not(feature = "std"), feature = "sgx"))] +extern crate sgx_tstd as std; + +pub mod ports; diff --git a/tee-worker/core-primitives/networking-utils/src/ports.rs b/tee-worker/core-primitives/networking-utils/src/ports.rs new file mode 100644 index 0000000000..4b8a523b27 --- /dev/null +++ b/tee-worker/core-primitives/networking-utils/src/ports.rs @@ -0,0 +1,48 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +use std::{net::TcpListener, ops::Range}; + +/// Gets the first available port in a range. +/// Returns None if no port in range is available. +/// +pub fn get_available_port_in_range(mut port_range: Range) -> Option { + port_range.find(|port| port_is_available(*port)) +} + +fn port_is_available(port: u16) -> bool { + TcpListener::bind(("127.0.0.1", port)).is_ok() +} + +#[cfg(test)] +mod tests { + use super::*; + use std::mem::drop; + + #[test] + fn port_is_not_available_when_bound() { + let available_port = get_available_port_in_range(12000..13000).unwrap(); + + let tcp_listener = TcpListener::bind(("127.0.0.1", available_port)).unwrap(); + + assert!(!port_is_available(available_port)); + + drop(tcp_listener); + + assert!(port_is_available(available_port)); + } +} diff --git a/tee-worker/core-primitives/node-api/Cargo.toml b/tee-worker/core-primitives/node-api/Cargo.toml new file mode 100644 index 0000000000..6996466617 --- /dev/null +++ b/tee-worker/core-primitives/node-api/Cargo.toml @@ -0,0 +1,28 @@ +[package] +authors = ["Integritee AG "] +edition = "2021" +name = "itp-node-api" +version = "0.9.0" + +[dependencies] +itp-api-client-extensions = { optional = true, path = "api-client-extensions" } +itp-api-client-types = { default-features = false, path = "api-client-types" } +itp-node-api-factory = { optional = true, path = "factory" } +itp-node-api-metadata = { default-features = false, path = "metadata" } +itp-node-api-metadata-provider = { default-features = false, path = "metadata-provider" } + +[features] +default = ["std"] +mocks = [ + "itp-node-api-metadata/mocks", +] +sgx = [ + "itp-node-api-metadata-provider/sgx", +] +std = [ + "itp-api-client-extensions", + "itp-api-client-types/std", + "itp-node-api-factory", + "itp-node-api-metadata/std", + "itp-node-api-metadata-provider/std", +] diff --git a/tee-worker/core-primitives/node-api/api-client-extensions/Cargo.toml b/tee-worker/core-primitives/node-api/api-client-extensions/Cargo.toml new file mode 100644 index 0000000000..01e84c2e75 --- /dev/null +++ b/tee-worker/core-primitives/node-api/api-client-extensions/Cargo.toml @@ -0,0 +1,25 @@ +[package] +authors = ["Integritee AG "] +edition = "2021" +name = "itp-api-client-extensions" +version = "0.9.0" + +[dependencies] +# crates.io +codec = { package = "parity-scale-codec", version = "3.0.0", features = ["derive"] } +thiserror = { version = "1.0" } + +# substrate +sp-core = { git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.29" } +sp-finality-grandpa = { git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.29" } +sp-runtime = { git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.29" } + +# scs +substrate-api-client = { git = "https://github.com/scs/substrate-api-client.git", branch = "polkadot-v0.9.29" } + +# local deps +itp-types = { path = "../../types" } + +[features] +# used for unit testing only! +mocks = [] diff --git a/tee-worker/core-primitives/node-api/api-client-extensions/src/account.rs b/tee-worker/core-primitives/node-api/api-client-extensions/src/account.rs new file mode 100644 index 0000000000..4703223328 --- /dev/null +++ b/tee-worker/core-primitives/node-api/api-client-extensions/src/account.rs @@ -0,0 +1,41 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +use crate::ApiResult; +use itp_types::AccountId; +use sp_core::crypto::Pair; +use sp_runtime::MultiSignature; +use substrate_api_client::{Api, ExtrinsicParams, RpcClient}; + +/// ApiClient extension that contains some convenience methods around accounts. +pub trait AccountApi { + fn get_nonce_of(&self, who: &AccountId) -> ApiResult; + fn get_free_balance(&self, who: &AccountId) -> ApiResult; +} + +impl AccountApi for Api +where + MultiSignature: From, +{ + fn get_nonce_of(&self, who: &AccountId) -> ApiResult { + Ok(self.get_account_info(who)?.map_or_else(|| 0, |info| info.nonce)) + } + + fn get_free_balance(&self, who: &AccountId) -> ApiResult { + Ok(self.get_account_data(who)?.map_or_else(|| 0, |data| data.free)) + } +} diff --git a/tee-worker/core-primitives/node-api/api-client-extensions/src/chain.rs b/tee-worker/core-primitives/node-api/api-client-extensions/src/chain.rs new file mode 100644 index 0000000000..39b9b785e6 --- /dev/null +++ b/tee-worker/core-primitives/node-api/api-client-extensions/src/chain.rs @@ -0,0 +1,102 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +use crate::ApiResult; +use itp_types::{Header, SignedBlock}; +use sp_core::{storage::StorageKey, Pair, H256}; +use sp_finality_grandpa::{AuthorityList, VersionedAuthorityList, GRANDPA_AUTHORITIES_KEY}; +use sp_runtime::MultiSignature; +use substrate_api_client::{Api, ExtrinsicParams, RpcClient}; + +pub type StorageProof = Vec>; + +/// ApiClient extension that simplifies chain data access. +pub trait ChainApi { + fn last_finalized_block(&self) -> ApiResult>; + fn signed_block(&self, hash: Option) -> ApiResult>; + fn get_genesis_hash(&self) -> ApiResult; + fn get_header(&self, header_hash: Option) -> ApiResult>; + /// Fetch blocks from parentchain with blocknumber from until to, including both boundaries. + /// Returns a vector with one element if from equals to. + /// Returns an empty vector if from is greater than to. + fn get_blocks(&self, from: u32, to: u32) -> ApiResult>; + fn is_grandpa_available(&self) -> ApiResult; + fn grandpa_authorities(&self, hash: Option) -> ApiResult; + fn grandpa_authorities_proof(&self, hash: Option) -> ApiResult; +} + +impl ChainApi for Api +where + MultiSignature: From, +{ + fn last_finalized_block(&self) -> ApiResult> { + self.get_finalized_head()? + .map_or_else(|| Ok(None), |hash| self.signed_block(Some(hash))) + } + + fn signed_block(&self, hash: Option) -> ApiResult> { + // Even though this is only a wrapper here, we want to have this in the trait + // to be able to be generic over the trait and mock the `signed_block` method + // in tests. + self.get_signed_block(hash) + } + + fn get_genesis_hash(&self) -> ApiResult { + self.get_genesis_hash() + } + + fn get_header(&self, header_hash: Option) -> ApiResult> { + self.get_header(header_hash) + } + + fn get_blocks(&self, from: u32, to: u32) -> ApiResult> { + let mut blocks = Vec::::new(); + + for n in from..=to { + if let Some(block) = self.get_signed_block_by_num(Some(n))? { + blocks.push(block); + } + } + Ok(blocks) + } + + fn is_grandpa_available(&self) -> ApiResult { + let genesis_hash = Some(self.get_genesis_hash().expect("Failed to get genesis hash")); + Ok(self + .get_storage_by_key_hash(StorageKey(GRANDPA_AUTHORITIES_KEY.to_vec()), genesis_hash)? + .map(|v: VersionedAuthorityList| v.into()) + .map(|v: AuthorityList| !v.is_empty()) + .unwrap_or(false)) + } + + fn grandpa_authorities(&self, at_block: Option) -> ApiResult { + Ok(self + .get_storage_by_key_hash(StorageKey(GRANDPA_AUTHORITIES_KEY.to_vec()), at_block)? + .map(|g: VersionedAuthorityList| g.into()) + .unwrap_or_default()) + } + + fn grandpa_authorities_proof(&self, at_block: Option) -> ApiResult { + Ok(self + .get_storage_proof_by_keys( + vec![StorageKey(GRANDPA_AUTHORITIES_KEY.to_vec())], + at_block, + )? + .map(|read_proof| read_proof.proof.into_iter().map(|bytes| bytes.0).collect()) + .unwrap_or_default()) + } +} diff --git a/tee-worker/core-primitives/node-api/api-client-extensions/src/lib.rs b/tee-worker/core-primitives/node-api/api-client-extensions/src/lib.rs new file mode 100644 index 0000000000..08004fa99f --- /dev/null +++ b/tee-worker/core-primitives/node-api/api-client-extensions/src/lib.rs @@ -0,0 +1,33 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +//! Some substrate-api-client extension traits. + +pub use substrate_api_client::{rpc::WsRpcClient, Api, ApiClientError}; + +pub mod account; +pub mod chain; +pub mod pallet_teeracle; +pub mod pallet_teerex; +pub mod pallet_teerex_api_mock; + +pub use account::*; +pub use chain::*; +pub use pallet_teeracle::*; +pub use pallet_teerex::*; + +pub type ApiResult = Result; diff --git a/tee-worker/core-primitives/node-api/api-client-extensions/src/pallet_teeracle.rs b/tee-worker/core-primitives/node-api/api-client-extensions/src/pallet_teeracle.rs new file mode 100644 index 0000000000..3f1ad2d198 --- /dev/null +++ b/tee-worker/core-primitives/node-api/api-client-extensions/src/pallet_teeracle.rs @@ -0,0 +1,19 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +pub const TEERACLE: &str = "Teeracle"; +pub const ADD_TO_WHITELIST: &str = "add_to_whitelist"; diff --git a/tee-worker/core-primitives/node-api/api-client-extensions/src/pallet_teerex.rs b/tee-worker/core-primitives/node-api/api-client-extensions/src/pallet_teerex.rs new file mode 100644 index 0000000000..4405f8169f --- /dev/null +++ b/tee-worker/core-primitives/node-api/api-client-extensions/src/pallet_teerex.rs @@ -0,0 +1,81 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +use crate::ApiResult; +use itp_types::{Enclave, IpfsHash, ShardIdentifier}; +use sp_core::{Pair, H256 as Hash}; +use sp_runtime::MultiSignature; +use substrate_api_client::{Api, ExtrinsicParams, RpcClient}; + +pub const TEEREX: &str = "Teerex"; +pub const SIDECHAIN: &str = "Sidechain"; + +/// ApiClient extension that enables communication with the `teerex` pallet. +pub trait PalletTeerexApi { + fn enclave(&self, index: u64, at_block: Option) -> ApiResult>; + fn enclave_count(&self, at_block: Option) -> ApiResult; + fn all_enclaves(&self, at_block: Option) -> ApiResult>; + fn worker_for_shard( + &self, + shard: &ShardIdentifier, + at_block: Option, + ) -> ApiResult>; + fn latest_ipfs_hash( + &self, + shard: &ShardIdentifier, + at_block: Option, + ) -> ApiResult>; +} + +impl PalletTeerexApi for Api +where + MultiSignature: From, +{ + fn enclave(&self, index: u64, at_block: Option) -> ApiResult> { + self.get_storage_map(TEEREX, "EnclaveRegistry", index, at_block) + } + + fn enclave_count(&self, at_block: Option) -> ApiResult { + Ok(self.get_storage_value(TEEREX, "EnclaveCount", at_block)?.unwrap_or(0u64)) + } + + fn all_enclaves(&self, at_block: Option) -> ApiResult> { + let count = self.enclave_count(at_block)?; + let mut enclaves = Vec::with_capacity(count as usize); + for n in 1..=count { + enclaves.push(self.enclave(n, at_block)?.expect("None enclave")) + } + Ok(enclaves) + } + + fn worker_for_shard( + &self, + shard: &ShardIdentifier, + at_block: Option, + ) -> ApiResult> { + self.get_storage_map(SIDECHAIN, "WorkerForShard", shard, at_block)? + .map_or_else(|| Ok(None), |w_index| self.enclave(w_index, at_block)) + } + + fn latest_ipfs_hash( + &self, + shard: &ShardIdentifier, + at_block: Option, + ) -> ApiResult> { + self.get_storage_map(TEEREX, "LatestIPFSHash", shard, at_block) + } +} diff --git a/tee-worker/core-primitives/node-api/api-client-extensions/src/pallet_teerex_api_mock.rs b/tee-worker/core-primitives/node-api/api-client-extensions/src/pallet_teerex_api_mock.rs new file mode 100644 index 0000000000..867b7ee5ff --- /dev/null +++ b/tee-worker/core-primitives/node-api/api-client-extensions/src/pallet_teerex_api_mock.rs @@ -0,0 +1,61 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +use crate::{pallet_teerex::PalletTeerexApi, ApiResult}; +use itp_types::{Enclave, IpfsHash, ShardIdentifier, H256 as Hash}; + +#[derive(Default)] +pub struct PalletTeerexApiMock { + registered_enclaves: Vec, +} + +impl PalletTeerexApiMock { + pub fn with_enclaves(mut self, enclaves: Vec) -> Self { + self.registered_enclaves.extend(enclaves); + self + } +} + +impl PalletTeerexApi for PalletTeerexApiMock { + fn enclave(&self, index: u64, _at_block: Option) -> ApiResult> { + Ok(self.registered_enclaves.get(index as usize).cloned()) + } + + fn enclave_count(&self, _at_block: Option) -> ApiResult { + Ok(self.registered_enclaves.len() as u64) + } + + fn all_enclaves(&self, _at_block: Option) -> ApiResult> { + Ok(self.registered_enclaves.clone()) + } + + fn worker_for_shard( + &self, + _shard: &ShardIdentifier, + _at_block: Option, + ) -> ApiResult> { + todo!() + } + + fn latest_ipfs_hash( + &self, + _shard: &ShardIdentifier, + _at_block: Option, + ) -> ApiResult> { + todo!() + } +} diff --git a/tee-worker/core-primitives/node-api/api-client-types/Cargo.toml b/tee-worker/core-primitives/node-api/api-client-types/Cargo.toml new file mode 100644 index 0000000000..5da5fc447a --- /dev/null +++ b/tee-worker/core-primitives/node-api/api-client-types/Cargo.toml @@ -0,0 +1,22 @@ +[package] +authors = ["Integritee AG "] +edition = "2021" +name = "itp-api-client-types" +version = "0.9.0" + +[dependencies] + +# scs +substrate-api-client = { default-features = false, git = "https://github.com/scs/substrate-api-client.git", branch = "polkadot-v0.9.29" } + +# substrate +sp-core = { optional = true, git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.29" } + + +[features] +default = ["std"] +std = [ + "substrate-api-client/std", + "substrate-api-client/ws-client", + "sp-core", +] diff --git a/tee-worker/core-primitives/node-api/api-client-types/src/lib.rs b/tee-worker/core-primitives/node-api/api-client-types/src/lib.rs new file mode 100644 index 0000000000..14b68e5271 --- /dev/null +++ b/tee-worker/core-primitives/node-api/api-client-types/src/lib.rs @@ -0,0 +1,56 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +//! Contains type definitions to talk to the node. +//! +//! You need to update this if you have a signed extension in your node that +//! is different from the integritee-node, e.g., if you use the `pallet_asset_tx_payment`. + +#![cfg_attr(not(feature = "std"), no_std)] + +pub use substrate_api_client::{ + PlainTip, PlainTipExtrinsicParams, PlainTipExtrinsicParamsBuilder, SubstrateDefaultSignedExtra, + UncheckedExtrinsicV4, +}; + +/// Configuration for the ExtrinsicParams. +/// +/// Valid for the default integritee node +pub type ParentchainExtrinsicParams = PlainTipExtrinsicParams; +pub type ParentchainExtrinsicParamsBuilder = PlainTipExtrinsicParamsBuilder; + +// Pay in asset fees. +// +// This needs to be used if the node uses the `pallet_asset_tx_payment`. +//pub type ParentchainExtrinsicParams = AssetTipExtrinsicParams; +//pub type ParentchainExtrinsicParamsBuilder = AssetTipExtrinsicParamsBuilder; + +pub type ParentchainUncheckedExtrinsic = + UncheckedExtrinsicV4>; + +#[cfg(feature = "std")] +pub use api::*; + +#[cfg(feature = "std")] +mod api { + use super::ParentchainExtrinsicParams; + use substrate_api_client::Api; + + pub use substrate_api_client::{rpc::WsRpcClient, ApiClientError}; + + pub type ParentchainApi = Api; +} diff --git a/tee-worker/core-primitives/node-api/factory/Cargo.toml b/tee-worker/core-primitives/node-api/factory/Cargo.toml new file mode 100644 index 0000000000..e989a8d15a --- /dev/null +++ b/tee-worker/core-primitives/node-api/factory/Cargo.toml @@ -0,0 +1,14 @@ +[package] +authors = ["Integritee AG "] +edition = "2021" +name = "itp-node-api-factory" +version = "0.9.0" + +[dependencies] +thiserror = { version = "1.0" } + +# substrate +sp-core = { git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.29" } + +# local +itp-api-client-types = { path = "../api-client-types" } diff --git a/tee-worker/core-primitives/node-api/factory/src/lib.rs b/tee-worker/core-primitives/node-api/factory/src/lib.rs new file mode 100644 index 0000000000..3c15d66aec --- /dev/null +++ b/tee-worker/core-primitives/node-api/factory/src/lib.rs @@ -0,0 +1,56 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + Copyright (C) 2017-2019 Baidu, Inc. All Rights Reserved. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +use itp_api_client_types::{ParentchainApi, WsRpcClient}; +use sp_core::sr25519; + +/// Trait to create a node API, based on a node URL and signer. +pub trait CreateNodeApi { + fn create_api(&self) -> Result; +} + +/// Node API factory error. +#[derive(Debug, thiserror::Error)] +pub enum NodeApiFactoryError { + #[error("Failed to create a node API: {0}")] + FailedToCreateNodeApi(#[from] itp_api_client_types::ApiClientError), + #[error(transparent)] + Other(#[from] Box), +} + +pub type Result = std::result::Result; + +/// Node API factory implementation. +pub struct NodeApiFactory { + node_url: String, + signer: sr25519::Pair, +} + +impl NodeApiFactory { + pub fn new(url: String, signer: sr25519::Pair) -> Self { + NodeApiFactory { node_url: url, signer } + } +} + +impl CreateNodeApi for NodeApiFactory { + fn create_api(&self) -> Result { + ParentchainApi::new(WsRpcClient::new(self.node_url.as_str())) + .map_err(NodeApiFactoryError::FailedToCreateNodeApi) + .map(|a| a.set_signer(self.signer.clone())) + } +} diff --git a/tee-worker/core-primitives/node-api/metadata-provider/Cargo.toml b/tee-worker/core-primitives/node-api/metadata-provider/Cargo.toml new file mode 100644 index 0000000000..4f5f2ad5b2 --- /dev/null +++ b/tee-worker/core-primitives/node-api/metadata-provider/Cargo.toml @@ -0,0 +1,32 @@ +[package] +authors = ["Integritee AG "] +edition = "2021" +name = "itp-node-api-metadata-provider" +version = "0.9.0" + +[dependencies] +# crates.io + +# sgx dependencies +sgx_tstd = { branch = "master", git = "https://github.com/apache/teaclave-sgx-sdk.git", optional = true } + +# sgx enabled external libraries +thiserror_sgx = { optional = true, package = "thiserror", git = "https://github.com/mesalock-linux/thiserror-sgx", tag = "sgx_1.1.3" } + +# std compatible external libraries (make sure these versions match with the sgx-enabled ones above) +thiserror = { version = "1.0", optional = true } + +# local dependencies +itp-node-api-metadata = { default-features = false, path = "../metadata" } + +[features] +default = ["std"] +sgx = [ + "sgx_tstd", + "thiserror_sgx", +] +std = [ + "thiserror", +] +# used for unit testing only! +mocks = [] diff --git a/tee-worker/core-primitives/node-api/metadata-provider/src/error.rs b/tee-worker/core-primitives/node-api/metadata-provider/src/error.rs new file mode 100644 index 0000000000..e8c67d87ec --- /dev/null +++ b/tee-worker/core-primitives/node-api/metadata-provider/src/error.rs @@ -0,0 +1,37 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +#[cfg(feature = "sgx")] +extern crate thiserror_sgx as thiserror; + +#[derive(Debug, PartialEq, Eq, thiserror::Error)] +pub enum Error { + /// Metadata has not been set + #[error("Metadata has no been set")] + MetadataNotSet, + /// Node metadata error + #[error("Metadata Error: {0:?}")] + MetadataError(itp_node_api_metadata::error::Error), +} + +pub type Result = core::result::Result; + +impl From for Error { + fn from(e: itp_node_api_metadata::error::Error) -> Self { + Self::MetadataError(e) + } +} diff --git a/tee-worker/core-primitives/node-api/metadata-provider/src/lib.rs b/tee-worker/core-primitives/node-api/metadata-provider/src/lib.rs new file mode 100644 index 0000000000..c63a028613 --- /dev/null +++ b/tee-worker/core-primitives/node-api/metadata-provider/src/lib.rs @@ -0,0 +1,117 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +//! Minimalistic crate for global metadata access withing the enclave. + +#![cfg_attr(not(feature = "std"), no_std)] +#![cfg_attr(test, feature(assert_matches))] + +#[cfg(all(feature = "std", feature = "sgx"))] +compile_error!("feature \"std\" and feature \"sgx\" cannot be enabled at the same time"); + +#[cfg(feature = "sgx")] +extern crate sgx_tstd as std; + +#[cfg(feature = "sgx")] +use std::sync::SgxRwLock as RwLock; + +#[cfg(feature = "std")] +use std::sync::RwLock; + +pub use crate::error::Error; + +use crate::error::Result; +use std::ops::Deref; + +pub mod error; + +/// Trait to get access to the node API metadata. +pub trait AccessNodeMetadata { + type MetadataType; + + fn get_from_metadata(&self, getter_function: F) -> Result + where + F: FnOnce(&Self::MetadataType) -> R; +} + +/// Repository to manage the node metadata. +/// +/// Provides simple means to set the metadata and read from it, guarded by a lock. +#[derive(Default)] +pub struct NodeMetadataRepository { + metadata_lock: RwLock>, +} + +impl NodeMetadataRepository +where + NodeMetadata: Default, +{ + pub fn new(metadata: NodeMetadata) -> Self { + NodeMetadataRepository { metadata_lock: RwLock::new(Some(metadata)) } + } + + pub fn set_metadata(&self, metadata: NodeMetadata) { + let mut metadata_lock = self.metadata_lock.write().expect("Lock poisoning"); + *metadata_lock = Some(metadata) + } +} + +impl AccessNodeMetadata for NodeMetadataRepository +where + NodeMetadata:, +{ + type MetadataType = NodeMetadata; + + fn get_from_metadata(&self, getter_function: F) -> Result + where + F: FnOnce(&Self::MetadataType) -> R, + { + match self.metadata_lock.read().expect("Lock poisoning").deref() { + Some(metadata) => Ok(getter_function(metadata)), + None => Err(Error::MetadataNotSet), + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use std::assert_matches::assert_matches; + + #[derive(Default)] + struct NodeMetadataMock; + + impl NodeMetadataMock { + fn get_one(&self) -> u32 { + 1 + } + } + #[test] + fn get_from_meta_data_returns_error_if_not_set() { + let repo = NodeMetadataRepository::::default(); + + assert_matches!(repo.get_from_metadata(|m| m.get_one()), Err(Error::MetadataNotSet)); + } + + #[test] + fn get_from_metadata_works() { + let repo = NodeMetadataRepository::::default(); + repo.set_metadata(NodeMetadataMock); + + assert_eq!(1, repo.get_from_metadata(|m| m.get_one()).unwrap()); + } +} diff --git a/tee-worker/core-primitives/node-api/metadata/Cargo.toml b/tee-worker/core-primitives/node-api/metadata/Cargo.toml new file mode 100644 index 0000000000..8699795366 --- /dev/null +++ b/tee-worker/core-primitives/node-api/metadata/Cargo.toml @@ -0,0 +1,26 @@ +[package] +authors = ["Integritee AG "] +edition = "2021" +name = "itp-node-api-metadata" +version = "0.9.0" + +[dependencies] +# crates.io +codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } + +# substrate +sp-core = { git = "https://github.com/paritytech/substrate.git", default-features = false, branch = "polkadot-v0.9.29" } + +# scs +substrate-api-client = { default-features = false, git = "https://github.com/scs/substrate-api-client.git", branch = "polkadot-v0.9.29" } + +[features] +default = ["std"] +std = [ + "codec/std", + "sp-core/std", + "substrate-api-client/std", +] + +# used for unit testing only! +mocks = [] diff --git a/tee-worker/core-primitives/node-api/metadata/src/error.rs b/tee-worker/core-primitives/node-api/metadata/src/error.rs new file mode 100644 index 0000000000..70e2e452b4 --- /dev/null +++ b/tee-worker/core-primitives/node-api/metadata/src/error.rs @@ -0,0 +1,26 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +#[derive(Debug, PartialEq, Eq)] +pub enum Error { + /// Metadata has not been set + MetadataNotSet, + /// Api-client metadata error + NodeMetadata(substrate_api_client::MetadataError), +} + +pub type Result = core::result::Result; diff --git a/tee-worker/core-primitives/node-api/metadata/src/lib.rs b/tee-worker/core-primitives/node-api/metadata/src/lib.rs new file mode 100644 index 0000000000..eaefbd54c6 --- /dev/null +++ b/tee-worker/core-primitives/node-api/metadata/src/lib.rs @@ -0,0 +1,130 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +//! Handle SGX compatible substrate chain metadata. + +#![cfg_attr(not(feature = "std"), no_std)] + +use crate::error::Result; +use codec::{Decode, Encode}; +use sp_core::storage::StorageKey; +use substrate_api_client::{Metadata, MetadataError}; + +pub use crate::error::Error; + +pub mod error; +pub mod pallet_imp; +pub mod pallet_imp_mock; +pub mod pallet_sidechain; +pub mod pallet_system; +pub mod pallet_teeracle; +pub mod pallet_teerex; + +#[cfg(feature = "mocks")] +pub mod metadata_mocks; + +#[derive(Default, Encode, Decode, Debug, Clone)] +pub struct NodeMetadata { + node_metadata: Option, + runtime_spec_version: u32, + runtime_transaction_version: u32, +} + +impl NodeMetadata { + pub fn new( + node_metadata: Metadata, + runtime_spec_version: u32, + runtime_transaction_version: u32, + ) -> Self { + Self { + node_metadata: Some(node_metadata), + runtime_spec_version, + runtime_transaction_version, + } + } + /// Return the substrate chain runtime version. + pub fn get_runtime_version(&self) -> u32 { + self.runtime_spec_version + } + + /// Return the substrate chain runtime transaction version. + pub fn get_runtime_transaction_version(&self) -> u32 { + self.runtime_transaction_version + } + + /// Generic call indexes: + /// Get the array [pallet index, call index] corresponding to a pallet's call over the metadata. + pub fn call_indexes( + &self, + pallet_name: &'static str, + call_name: &'static str, + ) -> Result<[u8; 2]> { + let pallet = match &self.node_metadata { + None => return Err(Error::MetadataNotSet), + Some(m) => m.pallet(pallet_name).map_err(Error::NodeMetadata)?, + }; + let call_index = pallet + .calls + .get(call_name) + .ok_or_else(|| Error::NodeMetadata(MetadataError::CallNotFound(call_name)))?; + Ok([pallet.index, *call_index]) + } + + /// Generic storages: + /// Get the storage keys corresponding to a storage over the metadata: + pub fn storage_value_key( + &self, + storage_prefix: &'static str, + storage_key_name: &'static str, + ) -> Result { + match &self.node_metadata { + None => Err(Error::MetadataNotSet), + Some(m) => m + .storage_value_key(storage_prefix, storage_key_name) + .map_err(Error::NodeMetadata), + } + } + + pub fn storage_map_key( + &self, + storage_prefix: &'static str, + storage_key_name: &'static str, + map_key: K, + ) -> Result { + match &self.node_metadata { + None => Err(Error::MetadataNotSet), + Some(m) => m + .storage_map_key::(storage_prefix, storage_key_name, map_key) + .map_err(Error::NodeMetadata), + } + } + + pub fn storage_double_map_key( + &self, + storage_prefix: &'static str, + storage_key_name: &'static str, + first: K, + second: Q, + ) -> Result { + match &self.node_metadata { + None => Err(Error::MetadataNotSet), + Some(m) => m + .storage_double_map_key(storage_prefix, storage_key_name, first, second) + .map_err(Error::NodeMetadata), + } + } +} diff --git a/tee-worker/core-primitives/node-api/metadata/src/metadata_mocks.rs b/tee-worker/core-primitives/node-api/metadata/src/metadata_mocks.rs new file mode 100644 index 0000000000..98f2709180 --- /dev/null +++ b/tee-worker/core-primitives/node-api/metadata/src/metadata_mocks.rs @@ -0,0 +1,222 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +use crate::{ + error::Result, pallet_imp::IMPCallIndexes, pallet_imp_mock::IMPMockCallIndexes, + pallet_sidechain::SidechainCallIndexes, pallet_teerex::TeerexCallIndexes, +}; +use codec::{Decode, Encode}; + +#[derive(Default, Encode, Decode, Debug, Clone)] +pub struct NodeMetadataMock { + teerex_module: u8, + register_enclave: u8, + unregister_enclave: u8, + call_worker: u8, + processed_parentchain_block: u8, + shield_funds: u8, + unshield_funds: u8, + sidechain_module: u8, + // litentry + // IMP + imp_module: u8, + imp_set_user_shielding_key: u8, + imp_link_identity: u8, + imp_unlink_identity: u8, + imp_verify_identity: u8, + imp_user_shielding_key_set: u8, + imp_challenge_code_generated: u8, + imp_identity_linked: u8, + imp_identity_unlinked: u8, + imp_identity_verified: u8, + imp_some_error: u8, + // IMP mock + imp_mock_module: u8, + imp_mock_set_user_shielding_key: u8, + imp_mock_link_identity: u8, + imp_mock_unlink_identity: u8, + imp_mock_verify_identity: u8, + imp_mock_user_shielding_key_set: u8, + imp_mock_challenge_code_generated: u8, + imp_mock_identity_linked: u8, + imp_mock_identity_unlinked: u8, + imp_mock_identity_verified: u8, + imp_mock_some_error: u8, + + imported_sidechain_block: u8, + runtime_spec_version: u32, + runtime_transaction_version: u32, +} + +impl NodeMetadataMock { + pub fn new() -> Self { + NodeMetadataMock { + teerex_module: 50u8, + register_enclave: 0u8, + unregister_enclave: 1u8, + call_worker: 2u8, + processed_parentchain_block: 3u8, + shield_funds: 4u8, + unshield_funds: 5u8, + sidechain_module: 53u8, + // litentry + imp_module: 64u8, + imp_set_user_shielding_key: 0u8, + imp_link_identity: 1u8, + imp_unlink_identity: 2u8, + imp_verify_identity: 3u8, + imp_user_shielding_key_set: 4u8, + imp_challenge_code_generated: 5u8, + imp_identity_linked: 6u8, + imp_identity_unlinked: 7u8, + imp_identity_verified: 8u8, + imp_some_error: 9u8, + + imp_mock_module: 100u8, + imp_mock_set_user_shielding_key: 0u8, + imp_mock_link_identity: 1u8, + imp_mock_unlink_identity: 2u8, + imp_mock_verify_identity: 3u8, + imp_mock_user_shielding_key_set: 4u8, + imp_mock_challenge_code_generated: 5u8, + imp_mock_identity_linked: 6u8, + imp_mock_identity_unlinked: 7u8, + imp_mock_identity_verified: 8u8, + imp_mock_some_error: 9u8, + + imported_sidechain_block: 0u8, + runtime_spec_version: 25, + runtime_transaction_version: 4, + } + } +} + +impl TeerexCallIndexes for NodeMetadataMock { + fn register_enclave_call_indexes(&self) -> Result<[u8; 2]> { + Ok([self.teerex_module, self.register_enclave]) + } + + fn unregister_enclave_call_indexes(&self) -> Result<[u8; 2]> { + Ok([self.teerex_module, self.unregister_enclave]) + } + + fn call_worker_call_indexes(&self) -> Result<[u8; 2]> { + Ok([self.teerex_module, self.call_worker]) + } + + fn confirm_processed_parentchain_block_call_indexes(&self) -> Result<[u8; 2]> { + Ok([self.teerex_module, self.processed_parentchain_block]) + } + + fn shield_funds_call_indexes(&self) -> Result<[u8; 2]> { + Ok([self.teerex_module, self.shield_funds]) + } + + fn unshield_funds_call_indexes(&self) -> Result<[u8; 2]> { + Ok([self.teerex_module, self.unshield_funds]) + } +} + +impl SidechainCallIndexes for NodeMetadataMock { + fn confirm_imported_sidechain_block_indexes(&self) -> Result<[u8; 2]> { + Ok([self.sidechain_module, self.imported_sidechain_block]) + } +} + +impl IMPCallIndexes for NodeMetadataMock { + fn set_user_shielding_key_call_indexes(&self) -> Result<[u8; 2]> { + Ok([self.imp_module, self.imp_set_user_shielding_key]) + } + + fn link_identity_call_indexes(&self) -> Result<[u8; 2]> { + Ok([self.imp_module, self.imp_link_identity]) + } + + fn unlink_identity_call_indexes(&self) -> Result<[u8; 2]> { + Ok([self.imp_module, self.imp_unlink_identity]) + } + + fn verify_identity_call_indexes(&self) -> Result<[u8; 2]> { + Ok([self.imp_module, self.imp_verify_identity]) + } + + fn user_shielding_key_set_call_indexes(&self) -> Result<[u8; 2]> { + Ok([self.imp_module, self.imp_user_shielding_key_set]) + } + + fn challenge_code_generated_call_indexes(&self) -> Result<[u8; 2]> { + Ok([self.imp_module, self.imp_challenge_code_generated]) + } + + fn identity_linked_call_indexes(&self) -> Result<[u8; 2]> { + Ok([self.imp_module, self.imp_identity_linked]) + } + + fn identity_unlinked_call_indexes(&self) -> Result<[u8; 2]> { + Ok([self.imp_module, self.imp_identity_unlinked]) + } + + fn identity_verified_call_indexes(&self) -> Result<[u8; 2]> { + Ok([self.imp_module, self.imp_identity_verified]) + } + + fn some_error_call_indexes(&self) -> Result<[u8; 2]> { + Ok([self.imp_module, self.imp_some_error]) + } +} + +impl IMPMockCallIndexes for NodeMetadataMock { + fn set_user_shielding_key_call_indexes(&self) -> Result<[u8; 2]> { + Ok([self.imp_mock_module, self.imp_mock_set_user_shielding_key]) + } + + fn link_identity_call_indexes(&self) -> Result<[u8; 2]> { + Ok([self.imp_mock_module, self.imp_mock_link_identity]) + } + + fn unlink_identity_call_indexes(&self) -> Result<[u8; 2]> { + Ok([self.imp_mock_module, self.imp_mock_unlink_identity]) + } + + fn verify_identity_call_indexes(&self) -> Result<[u8; 2]> { + Ok([self.imp_mock_module, self.imp_mock_verify_identity]) + } + + fn user_shielding_key_set_call_indexes(&self) -> Result<[u8; 2]> { + Ok([self.imp_mock_module, self.imp_mock_user_shielding_key_set]) + } + + fn challenge_code_generated_call_indexes(&self) -> Result<[u8; 2]> { + Ok([self.imp_mock_module, self.imp_mock_challenge_code_generated]) + } + + fn identity_linked_call_indexes(&self) -> Result<[u8; 2]> { + Ok([self.imp_mock_module, self.imp_mock_identity_linked]) + } + + fn identity_unlinked_call_indexes(&self) -> Result<[u8; 2]> { + Ok([self.imp_mock_module, self.imp_mock_identity_unlinked]) + } + + fn identity_verified_call_indexes(&self) -> Result<[u8; 2]> { + Ok([self.imp_mock_module, self.imp_mock_identity_verified]) + } + + fn some_error_call_indexes(&self) -> Result<[u8; 2]> { + Ok([self.imp_mock_module, self.imp_mock_some_error]) + } +} diff --git a/tee-worker/core-primitives/node-api/metadata/src/pallet_imp.rs b/tee-worker/core-primitives/node-api/metadata/src/pallet_imp.rs new file mode 100644 index 0000000000..54dc894cbb --- /dev/null +++ b/tee-worker/core-primitives/node-api/metadata/src/pallet_imp.rs @@ -0,0 +1,77 @@ +// Copyright 2020-2022 Litentry Technologies GmbH. +// This file is part of Litentry. +// +// Litentry is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Litentry is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Litentry. If not, see . + +// TODO: maybe use macros to simplify this +use crate::{error::Result, NodeMetadata}; + +/// Pallet' name: +const IMP: &str = "IdentityManagement"; + +pub trait IMPCallIndexes { + fn set_user_shielding_key_call_indexes(&self) -> Result<[u8; 2]>; + fn link_identity_call_indexes(&self) -> Result<[u8; 2]>; + fn unlink_identity_call_indexes(&self) -> Result<[u8; 2]>; + fn verify_identity_call_indexes(&self) -> Result<[u8; 2]>; + + fn user_shielding_key_set_call_indexes(&self) -> Result<[u8; 2]>; + fn challenge_code_generated_call_indexes(&self) -> Result<[u8; 2]>; + fn identity_linked_call_indexes(&self) -> Result<[u8; 2]>; + fn identity_unlinked_call_indexes(&self) -> Result<[u8; 2]>; + fn identity_verified_call_indexes(&self) -> Result<[u8; 2]>; + fn some_error_call_indexes(&self) -> Result<[u8; 2]>; +} + +impl IMPCallIndexes for NodeMetadata { + fn set_user_shielding_key_call_indexes(&self) -> Result<[u8; 2]> { + self.call_indexes(IMP, "set_user_shielding_key") + } + + fn link_identity_call_indexes(&self) -> Result<[u8; 2]> { + self.call_indexes(IMP, "link_identity") + } + + fn unlink_identity_call_indexes(&self) -> Result<[u8; 2]> { + self.call_indexes(IMP, "unlink_identity") + } + + fn verify_identity_call_indexes(&self) -> Result<[u8; 2]> { + self.call_indexes(IMP, "verify_identity") + } + + fn user_shielding_key_set_call_indexes(&self) -> Result<[u8; 2]> { + self.call_indexes(IMP, "user_shielding_key_set") + } + + fn challenge_code_generated_call_indexes(&self) -> Result<[u8; 2]> { + self.call_indexes(IMP, "challenge_code_generated") + } + + fn identity_linked_call_indexes(&self) -> Result<[u8; 2]> { + self.call_indexes(IMP, "identity_linked") + } + + fn identity_unlinked_call_indexes(&self) -> Result<[u8; 2]> { + self.call_indexes(IMP, "identity_unlinked") + } + + fn identity_verified_call_indexes(&self) -> Result<[u8; 2]> { + self.call_indexes(IMP, "identity_verified") + } + + fn some_error_call_indexes(&self) -> Result<[u8; 2]> { + self.call_indexes(IMP, "some_error") + } +} diff --git a/tee-worker/core-primitives/node-api/metadata/src/pallet_imp_mock.rs b/tee-worker/core-primitives/node-api/metadata/src/pallet_imp_mock.rs new file mode 100644 index 0000000000..f39f546112 --- /dev/null +++ b/tee-worker/core-primitives/node-api/metadata/src/pallet_imp_mock.rs @@ -0,0 +1,77 @@ +// Copyright 2020-2022 Litentry Technologies GmbH. +// This file is part of Litentry. +// +// Litentry is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Litentry is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Litentry. If not, see . + +// TODO: maybe use macros to simplify this +use crate::{error::Result, NodeMetadata}; + +/// Pallet' name: +const IMPMOCK: &str = "IdentityManagemenMock"; + +pub trait IMPMockCallIndexes { + fn set_user_shielding_key_call_indexes(&self) -> Result<[u8; 2]>; + fn link_identity_call_indexes(&self) -> Result<[u8; 2]>; + fn unlink_identity_call_indexes(&self) -> Result<[u8; 2]>; + fn verify_identity_call_indexes(&self) -> Result<[u8; 2]>; + + fn user_shielding_key_set_call_indexes(&self) -> Result<[u8; 2]>; + fn challenge_code_generated_call_indexes(&self) -> Result<[u8; 2]>; + fn identity_linked_call_indexes(&self) -> Result<[u8; 2]>; + fn identity_unlinked_call_indexes(&self) -> Result<[u8; 2]>; + fn identity_verified_call_indexes(&self) -> Result<[u8; 2]>; + fn some_error_call_indexes(&self) -> Result<[u8; 2]>; +} + +impl IMPMockCallIndexes for NodeMetadata { + fn set_user_shielding_key_call_indexes(&self) -> Result<[u8; 2]> { + self.call_indexes(IMPMOCK, "set_user_shielding_key") + } + + fn link_identity_call_indexes(&self) -> Result<[u8; 2]> { + self.call_indexes(IMPMOCK, "link_identity") + } + + fn unlink_identity_call_indexes(&self) -> Result<[u8; 2]> { + self.call_indexes(IMPMOCK, "unlink_identity") + } + + fn verify_identity_call_indexes(&self) -> Result<[u8; 2]> { + self.call_indexes(IMPMOCK, "verify_identity") + } + + fn user_shielding_key_set_call_indexes(&self) -> Result<[u8; 2]> { + self.call_indexes(IMPMOCK, "user_shielding_key_set") + } + + fn challenge_code_generated_call_indexes(&self) -> Result<[u8; 2]> { + self.call_indexes(IMPMOCK, "challenge_code_generated") + } + + fn identity_linked_call_indexes(&self) -> Result<[u8; 2]> { + self.call_indexes(IMPMOCK, "identity_linked") + } + + fn identity_unlinked_call_indexes(&self) -> Result<[u8; 2]> { + self.call_indexes(IMPMOCK, "identity_unlinked") + } + + fn identity_verified_call_indexes(&self) -> Result<[u8; 2]> { + self.call_indexes(IMPMOCK, "identity_verified") + } + + fn some_error_call_indexes(&self) -> Result<[u8; 2]> { + self.call_indexes(IMPMOCK, "some_error") + } +} diff --git a/tee-worker/core-primitives/node-api/metadata/src/pallet_sidechain.rs b/tee-worker/core-primitives/node-api/metadata/src/pallet_sidechain.rs new file mode 100644 index 0000000000..530e303a88 --- /dev/null +++ b/tee-worker/core-primitives/node-api/metadata/src/pallet_sidechain.rs @@ -0,0 +1,30 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +use crate::{error::Result, NodeMetadata}; +/// Pallet' name: +const SIDECHAIN: &str = "Sidechain"; + +pub trait SidechainCallIndexes { + fn confirm_imported_sidechain_block_indexes(&self) -> Result<[u8; 2]>; +} + +impl SidechainCallIndexes for NodeMetadata { + fn confirm_imported_sidechain_block_indexes(&self) -> Result<[u8; 2]> { + self.call_indexes(SIDECHAIN, "confirm_imported_sidechain_block") + } +} diff --git a/tee-worker/core-primitives/node-api/metadata/src/pallet_system.rs b/tee-worker/core-primitives/node-api/metadata/src/pallet_system.rs new file mode 100644 index 0000000000..87efc8d8da --- /dev/null +++ b/tee-worker/core-primitives/node-api/metadata/src/pallet_system.rs @@ -0,0 +1,37 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ +use crate::{error::Result, NodeMetadata}; +use sp_core::storage::StorageKey; + +/// Pallet' name: +const SYSTEM: &str = "System"; + +pub trait SystemStorageIndexes { + fn system_account_storage_key(&self) -> Result; + + fn system_account_storage_map_key(&self, index: u64) -> Result; +} + +impl SystemStorageIndexes for NodeMetadata { + fn system_account_storage_key(&self) -> Result { + self.storage_value_key(SYSTEM, "Account") + } + + fn system_account_storage_map_key(&self, index: u64) -> Result { + self.storage_map_key(SYSTEM, "Account", index) + } +} diff --git a/tee-worker/core-primitives/node-api/metadata/src/pallet_teeracle.rs b/tee-worker/core-primitives/node-api/metadata/src/pallet_teeracle.rs new file mode 100644 index 0000000000..fa25168a4f --- /dev/null +++ b/tee-worker/core-primitives/node-api/metadata/src/pallet_teeracle.rs @@ -0,0 +1,41 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +use crate::{error::Result, NodeMetadata}; + +/// Pallet' name: +const TEERACLE: &str = "Teeracle"; + +pub trait TeeracleCallIndexes { + fn add_to_whitelist_call_indexes(&self) -> Result<[u8; 2]>; + fn remove_from_whitelist_call_indexes(&self) -> Result<[u8; 2]>; + fn update_exchange_rate_call_indexes(&self) -> Result<[u8; 2]>; +} + +impl TeeracleCallIndexes for NodeMetadata { + fn add_to_whitelist_call_indexes(&self) -> Result<[u8; 2]> { + self.call_indexes(TEERACLE, "add_to_whitelist") + } + + fn remove_from_whitelist_call_indexes(&self) -> Result<[u8; 2]> { + self.call_indexes(TEERACLE, "remove_from_whitelist") + } + + fn update_exchange_rate_call_indexes(&self) -> Result<[u8; 2]> { + self.call_indexes(TEERACLE, "update_exchange_rate") + } +} diff --git a/tee-worker/core-primitives/node-api/metadata/src/pallet_teerex.rs b/tee-worker/core-primitives/node-api/metadata/src/pallet_teerex.rs new file mode 100644 index 0000000000..0dfad40691 --- /dev/null +++ b/tee-worker/core-primitives/node-api/metadata/src/pallet_teerex.rs @@ -0,0 +1,77 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ +use crate::{error::Result, NodeMetadata}; +use sp_core::storage::StorageKey; + +/// Pallet' name: +const TEEREX: &str = "Teerex"; + +pub trait TeerexCallIndexes { + fn register_enclave_call_indexes(&self) -> Result<[u8; 2]>; + + fn unregister_enclave_call_indexes(&self) -> Result<[u8; 2]>; + + fn call_worker_call_indexes(&self) -> Result<[u8; 2]>; + + fn confirm_processed_parentchain_block_call_indexes(&self) -> Result<[u8; 2]>; + + fn shield_funds_call_indexes(&self) -> Result<[u8; 2]>; + + fn unshield_funds_call_indexes(&self) -> Result<[u8; 2]>; +} + +pub trait TeerexStorageKey { + fn enclave_count_storage_key(&self) -> Result; + + fn enclave_registry_storage_map_key(&self, index: u64) -> Result; +} + +impl TeerexCallIndexes for NodeMetadata { + fn register_enclave_call_indexes(&self) -> Result<[u8; 2]> { + self.call_indexes(TEEREX, "register_enclave") + } + + fn unregister_enclave_call_indexes(&self) -> Result<[u8; 2]> { + self.call_indexes(TEEREX, "unregister_enclave") + } + + fn call_worker_call_indexes(&self) -> Result<[u8; 2]> { + self.call_indexes(TEEREX, "call_worker") + } + + fn confirm_processed_parentchain_block_call_indexes(&self) -> Result<[u8; 2]> { + self.call_indexes(TEEREX, "confirm_processed_parentchain_block") + } + + fn shield_funds_call_indexes(&self) -> Result<[u8; 2]> { + self.call_indexes(TEEREX, "shield_funds") + } + + fn unshield_funds_call_indexes(&self) -> Result<[u8; 2]> { + self.call_indexes(TEEREX, "unshield_funds") + } +} + +impl TeerexStorageKey for NodeMetadata { + fn enclave_count_storage_key(&self) -> Result { + self.storage_value_key(TEEREX, "EnclaveCount") + } + + fn enclave_registry_storage_map_key(&self, index: u64) -> Result { + self.storage_map_key(TEEREX, "EnclaveRegistry", index) + } +} diff --git a/tee-worker/core-primitives/node-api/src/lib.rs b/tee-worker/core-primitives/node-api/src/lib.rs new file mode 100644 index 0000000000..aea624c771 --- /dev/null +++ b/tee-worker/core-primitives/node-api/src/lib.rs @@ -0,0 +1,37 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +//! Re-export crate for all the node-api stuff to simplify downstream imports. + +#![cfg_attr(not(feature = "std"), no_std)] + +#[cfg(all(feature = "std", feature = "sgx"))] +compile_error!("feature \"std\" and feature \"sgx\" cannot be enabled at the same time"); + +#[cfg(feature = "std")] +pub use itp_node_api_factory as node_api_factory; + +pub mod api_client { + #[cfg(feature = "std")] + pub use itp_api_client_extensions::*; + pub use itp_api_client_types::*; +} + +pub mod metadata { + pub use itp_node_api_metadata::*; + pub use itp_node_api_metadata_provider as provider; +} diff --git a/tee-worker/core-primitives/nonce-cache/Cargo.toml b/tee-worker/core-primitives/nonce-cache/Cargo.toml new file mode 100644 index 0000000000..ab52eeef20 --- /dev/null +++ b/tee-worker/core-primitives/nonce-cache/Cargo.toml @@ -0,0 +1,30 @@ +[package] +authors = ["Integritee AG "] +edition = "2021" +name = "itp-nonce-cache" +version = "0.8.0" + +[dependencies] +# sgx dependencies +sgx_tstd = { branch = "master", git = "https://github.com/apache/teaclave-sgx-sdk.git", optional = true } + +# local dependencies + +# sgx enabled external libraries +thiserror_sgx = { package = "thiserror", git = "https://github.com/mesalock-linux/thiserror-sgx", tag = "sgx_1.1.3", optional = true } + +# std compatible external libraries (make sure these versions match with the sgx-enabled ones above) +thiserror = { version = "1.0", optional = true } + +# no-std dependencies +lazy_static = { version = "1.1.0", features = ["spin_no_std"] } + +[features] +default = ["std"] +sgx = [ + "sgx_tstd", + "thiserror_sgx", +] +std = [ + "thiserror", +] diff --git a/tee-worker/core-primitives/nonce-cache/src/error.rs b/tee-worker/core-primitives/nonce-cache/src/error.rs new file mode 100644 index 0000000000..6b1731a77e --- /dev/null +++ b/tee-worker/core-primitives/nonce-cache/src/error.rs @@ -0,0 +1,32 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +#[cfg(all(not(feature = "std"), feature = "sgx"))] +use crate::sgx_reexport_prelude::*; + +use std::boxed::Box; + +pub type Result = core::result::Result; + +/// nonce cache error +#[derive(Debug, thiserror::Error)] +pub enum Error { + #[error("Nonce lock is poisoned")] + LockPoisoning, + #[error(transparent)] + Other(#[from] Box), +} diff --git a/tee-worker/core-primitives/nonce-cache/src/lib.rs b/tee-worker/core-primitives/nonce-cache/src/lib.rs new file mode 100644 index 0000000000..e28061cf1d --- /dev/null +++ b/tee-worker/core-primitives/nonce-cache/src/lib.rs @@ -0,0 +1,73 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +#![cfg_attr(not(feature = "std"), no_std)] +#![feature(assert_matches)] + +#[cfg(all(feature = "std", feature = "sgx"))] +compile_error!("feature \"std\" and feature \"sgx\" cannot be enabled at the same time"); + +#[cfg(all(not(feature = "std"), feature = "sgx"))] +extern crate sgx_tstd as std; + +// re-export module to properly feature gate sgx and regular std environment +#[cfg(all(not(feature = "std"), feature = "sgx"))] +pub mod sgx_reexport_prelude { + pub use thiserror_sgx as thiserror; +} + +#[cfg(feature = "std")] +use std::sync::RwLockWriteGuard; +#[cfg(all(not(feature = "std"), feature = "sgx"))] +use std::sync::SgxRwLockWriteGuard as RwLockWriteGuard; + +use crate::error::Result; +use lazy_static::lazy_static; +use std::sync::Arc; + +pub use nonce_cache::NonceCache; + +lazy_static! { + /// Global instance of a nonce cache + /// + /// Concurrent access is managed internally, using RW locks + pub static ref GLOBAL_NONCE_CACHE: Arc = Default::default(); +} + +pub mod error; +pub mod nonce_cache; + +pub type NonceValue = u32; + +/// Nonce type (newtype wrapper for NonceValue) +#[derive(Default, Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd)] +pub struct Nonce(pub NonceValue); +/// Trait to mutate a nonce. +/// +/// Used in a combination of loading a lock and then writing the updated +/// value back, returning the lock again. +pub trait MutateNonce { + /// load a nonce with the intention to mutate it. lock is released once it goes out of scope + fn load_for_mutation(&self) -> Result>; +} + +/// Trait to get a nonce. +/// +/// +pub trait GetNonce { + fn get_nonce(&self) -> Result; +} diff --git a/tee-worker/core-primitives/nonce-cache/src/nonce_cache.rs b/tee-worker/core-primitives/nonce-cache/src/nonce_cache.rs new file mode 100644 index 0000000000..af55045cd0 --- /dev/null +++ b/tee-worker/core-primitives/nonce-cache/src/nonce_cache.rs @@ -0,0 +1,101 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +#[cfg(all(not(feature = "std"), feature = "sgx"))] +use std::sync::SgxRwLock as RwLock; +#[cfg(all(not(feature = "std"), feature = "sgx"))] +use std::sync::SgxRwLockWriteGuard as RwLockWriteGuard; + +#[cfg(feature = "std")] +use std::sync::RwLock; +#[cfg(feature = "std")] +use std::sync::RwLockWriteGuard; + +use crate::{ + error::{Error, Result}, + GetNonce, MutateNonce, Nonce, +}; + +/// Local nonce cache +/// +/// stores the nonce internally, protected by a RW lock for concurrent access +#[derive(Default)] +pub struct NonceCache { + nonce_lock: RwLock, +} + +impl NonceCache { + pub fn new(nonce_lock: RwLock) -> Self { + NonceCache { nonce_lock } + } +} + +impl MutateNonce for NonceCache { + fn load_for_mutation(&self) -> Result> { + self.nonce_lock.write().map_err(|_| Error::LockPoisoning) + } +} + +impl GetNonce for NonceCache { + fn get_nonce(&self) -> Result { + let nonce_lock = self.nonce_lock.read().map_err(|_| Error::LockPoisoning)?; + Ok(*nonce_lock) + } +} + +#[cfg(test)] +pub mod tests { + use super::*; + use std::{sync::Arc, thread}; + + #[test] + pub fn nonce_defaults_to_zero() { + let nonce_cache = NonceCache::default(); + assert_eq!(Nonce(0), nonce_cache.get_nonce().unwrap()); + } + + #[test] + pub fn set_nonce_works() { + let nonce_cache = NonceCache::default(); + let mut nonce_lock = nonce_cache.load_for_mutation().unwrap(); + *nonce_lock = Nonce(42); + std::mem::drop(nonce_lock); + assert_eq!(Nonce(42), nonce_cache.get_nonce().unwrap()); + } + + #[test] + pub fn concurrent_read_access_blocks_until_write_is_done() { + let nonce_cache = Arc::new(NonceCache::default()); + + let mut nonce_write_lock = nonce_cache.load_for_mutation().unwrap(); + + // spawn a new thread that reads the nonce + // this thread should be blocked until the write lock is released, i.e. until + // the new nonce is written. We can verify this, by trying to read that nonce variable + // that will be inserted further down below + let new_thread_nonce_cache = nonce_cache.clone(); + let join_handle = thread::spawn(move || { + let nonce_read = new_thread_nonce_cache.get_nonce().unwrap(); + assert_eq!(Nonce(3108), nonce_read); + }); + + *nonce_write_lock = Nonce(3108); + std::mem::drop(nonce_write_lock); + + join_handle.join().unwrap(); + } +} diff --git a/tee-worker/core-primitives/ocall-api/Cargo.toml b/tee-worker/core-primitives/ocall-api/Cargo.toml new file mode 100644 index 0000000000..add8e2a265 --- /dev/null +++ b/tee-worker/core-primitives/ocall-api/Cargo.toml @@ -0,0 +1,32 @@ +[package] +authors = ["Integritee AG "] +edition = "2021" +name = "itp-ocall-api" +version = "0.9.0" + +[dependencies] +codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } +derive_more = { version = "0.99.5" } + +# sgx deps +sgx_types = { branch = "master", git = "https://github.com/apache/teaclave-sgx-sdk.git" } + +# substrate deps +sp-core = { default-features = false, git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.29" } +sp-runtime = { default-features = false, git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.29" } +sp-std = { default-features = false, git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.29" } + +# local deps +itp-storage = { path = "../storage", default-features = false } +itp-types = { path = "../types", default-features = false } + +[features] +default = ["std"] +std = [ + "codec/std", + "sp-core/std", + "sp-runtime/std", + "sp-std/std", + "itp-storage/std", + "itp-types/std", +] diff --git a/tee-worker/core-primitives/ocall-api/src/lib.rs b/tee-worker/core-primitives/ocall-api/src/lib.rs new file mode 100644 index 0000000000..3bf139a58d --- /dev/null +++ b/tee-worker/core-primitives/ocall-api/src/lib.rs @@ -0,0 +1,133 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +#![cfg_attr(not(feature = "std"), no_std)] + +pub extern crate alloc; + +use alloc::vec::Vec; +use codec::{Decode, Encode}; +use core::result::Result as StdResult; +use derive_more::{Display, From}; +use itp_storage::Error as StorageError; +use itp_types::{ + storage::StorageEntryVerified, BlockHash, ShardIdentifier, TrustedOperationStatus, + WorkerRequest, WorkerResponse, +}; +use sgx_types::*; +use sp_core::H256; +use sp_runtime::{traits::Header, OpaqueExtrinsic}; +use sp_std::prelude::*; + +#[derive(Debug, Display, From)] +pub enum Error { + Storage(StorageError), + Codec(codec::Error), + Sgx(sgx_types::sgx_status_t), +} + +pub type Result = StdResult; +/// Trait for the enclave to make o-calls related to remote attestation +pub trait EnclaveAttestationOCallApi: Clone + Send + Sync { + fn sgx_init_quote(&self) -> SgxResult<(sgx_target_info_t, sgx_epid_group_id_t)>; + + fn get_ias_socket(&self) -> SgxResult; + + fn get_quote( + &self, + sig_rl: Vec, + report: sgx_report_t, + sign_type: sgx_quote_sign_type_t, + spid: sgx_spid_t, + quote_nonce: sgx_quote_nonce_t, + ) -> SgxResult<(sgx_report_t, Vec)>; + + fn get_update_info( + &self, + platform_info: sgx_platform_info_t, + enclave_trusted: i32, + ) -> SgxResult; + + fn get_mrenclave_of_self(&self) -> SgxResult; +} + +/// trait for o-calls related to RPC +pub trait EnclaveRpcOCallApi: Clone + Send + Sync + Default { + fn update_status_event( + &self, + hash: H, + status_update: TrustedOperationStatus, + ) -> SgxResult<()>; + + fn send_state(&self, hash: H, value_opt: Option>) -> SgxResult<()>; +} + +/// trait for o-calls related to on-chain interactions +pub trait EnclaveOnChainOCallApi: Clone + Send + Sync { + fn send_to_parentchain(&self, extrinsics: Vec) -> SgxResult<()>; + + fn worker_request( + &self, + req: Vec, + ) -> SgxResult>>; + + fn get_storage_verified, V: Decode>( + &self, + storage_hash: Vec, + header: &H, + ) -> Result>; + + fn get_multiple_storages_verified, V: Decode>( + &self, + storage_hashes: Vec>, + header: &H, + ) -> Result>>; +} + +/// Trait for sending metric updates. +pub trait EnclaveMetricsOCallApi: Clone + Send + Sync { + fn update_metric(&self, metric: Metric) -> SgxResult<()>; +} + +pub trait EnclaveSidechainOCallApi: Clone + Send + Sync { + fn propose_sidechain_blocks( + &self, + signed_blocks: Vec, + ) -> SgxResult<()>; + + fn store_sidechain_blocks( + &self, + signed_blocks: Vec, + ) -> SgxResult<()>; + + fn fetch_sidechain_blocks_from_peer( + &self, + last_imported_block_hash: BlockHash, + maybe_until_block_hash: Option, + shard_identifier: ShardIdentifier, + ) -> SgxResult>; +} + +/// Newtype for IPFS CID +pub struct IpfsCid(pub [u8; 46]); + +/// trait for o-call related to IPFS +pub trait EnclaveIpfsOCallApi: Clone + Send + Sync { + fn write_ipfs(&self, encoded_state: &[u8]) -> SgxResult; + + fn read_ipfs(&self, cid: &IpfsCid) -> SgxResult<()>; +} diff --git a/tee-worker/core-primitives/primitives-cache/Cargo.toml b/tee-worker/core-primitives/primitives-cache/Cargo.toml new file mode 100644 index 0000000000..a9a27f3df9 --- /dev/null +++ b/tee-worker/core-primitives/primitives-cache/Cargo.toml @@ -0,0 +1,30 @@ +[package] +authors = ["Integritee AG "] +edition = "2021" +name = "itp-primitives-cache" +version = "0.9.0" + +[dependencies] +# sgx dependencies +sgx_tstd = { branch = "master", git = "https://github.com/apache/teaclave-sgx-sdk.git", optional = true } + +# local dependencies + +# sgx enabled external libraries +thiserror_sgx = { package = "thiserror", git = "https://github.com/mesalock-linux/thiserror-sgx", tag = "sgx_1.1.3", optional = true } + +# std compatible external libraries (make sure these versions match with the sgx-enabled ones above) +thiserror = { version = "1.0", optional = true } + +# no-std dependencies +lazy_static = { version = "1.1.0", features = ["spin_no_std"] } + +[features] +default = ["std"] +sgx = [ + "sgx_tstd", + "thiserror_sgx", +] +std = [ + "thiserror", +] diff --git a/tee-worker/core-primitives/primitives-cache/src/error.rs b/tee-worker/core-primitives/primitives-cache/src/error.rs new file mode 100644 index 0000000000..2873dd8156 --- /dev/null +++ b/tee-worker/core-primitives/primitives-cache/src/error.rs @@ -0,0 +1,31 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +#[cfg(all(not(feature = "std"), feature = "sgx"))] +use crate::sgx_reexport_prelude::*; + +use std::boxed::Box; + +pub type Result = core::result::Result; + +#[derive(Debug, thiserror::Error)] +pub enum Error { + #[error("Primitives lock is poisoned")] + LockPoisoning, + #[error(transparent)] + Other(#[from] Box), +} diff --git a/tee-worker/core-primitives/primitives-cache/src/lib.rs b/tee-worker/core-primitives/primitives-cache/src/lib.rs new file mode 100644 index 0000000000..e4a2724e3f --- /dev/null +++ b/tee-worker/core-primitives/primitives-cache/src/lib.rs @@ -0,0 +1,114 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +//! Stores all primitives of the enclave that do need to be accessed often, but are +//! not be frequently mutated, such as keys and server urls. +//! +//! TODO: For now only the mu-ra server and untrusted worker url is stored here. Keys and such could also be stored here. + +#![cfg_attr(not(feature = "std"), no_std)] +#![feature(assert_matches)] + +#[cfg(all(feature = "std", feature = "sgx"))] +compile_error!("feature \"std\" and feature \"sgx\" cannot be enabled at the same time"); + +#[cfg(all(not(feature = "std"), feature = "sgx"))] +extern crate sgx_tstd as std; + +// Re-export module to properly feature gate sgx and regular std environment. +#[cfg(all(not(feature = "std"), feature = "sgx"))] +pub mod sgx_reexport_prelude { + pub use thiserror_sgx as thiserror; +} + +#[cfg(feature = "std")] +use std::sync::RwLockReadGuard; +#[cfg(feature = "std")] +use std::sync::RwLockWriteGuard; + +#[cfg(all(not(feature = "std"), feature = "sgx"))] +use std::sync::SgxRwLockReadGuard as RwLockReadGuard; +#[cfg(all(not(feature = "std"), feature = "sgx"))] +use std::sync::SgxRwLockWriteGuard as RwLockWriteGuard; + +use crate::error::Result; +use lazy_static::lazy_static; +use std::{string::String, sync::Arc}; + +pub use primitives_cache::PrimitivesCache; + +lazy_static! { + /// Global instance of the primitives cache. + /// + /// Concurrent access is managed internally, using RW locks. + pub static ref GLOBAL_PRIMITIVES_CACHE: Arc = Default::default(); +} + +pub mod error; +pub mod primitives_cache; + +#[derive(Default, Clone, Debug, Eq, PartialEq, Ord, PartialOrd)] +pub struct Primitives { + mu_ra_url: String, + untrusted_worker_url: String, +} + +impl Primitives { + pub fn new(mu_ra_url: String, untrusted_worker_url: String) -> Primitives { + Primitives { mu_ra_url, untrusted_worker_url } + } + + pub fn mu_ra_url(&self) -> &str { + &self.mu_ra_url + } + + pub fn untrusted_worker_url(&self) -> &str { + &self.untrusted_worker_url + } +} + +/// Trait to mutate the primitives. +/// +/// Used in a combination of loading a lock and then writing the updated +/// value back, returning the lock again. +pub trait MutatePrimitives { + fn load_for_mutation(&self) -> Result>; +} + +/// Trait to get the primitives. +pub trait GetPrimitives { + /// Returns a clone of the full Primitives struct. + fn get_primitives(&self) -> Result>; + + fn get_mu_ra_url(&self) -> Result; + + fn get_untrusted_worker_url(&self) -> Result; +} + +// Helper function to set primitives of a given cache. +pub fn set_primitives( + cache: &E, + mu_ra_url: String, + untrusted_worker_url: String, +) -> Result<()> { + let primitives = Primitives::new(mu_ra_url, untrusted_worker_url); + let mut rw_lock = cache.load_for_mutation()?; + + *rw_lock = primitives; + + Ok(()) +} diff --git a/tee-worker/core-primitives/primitives-cache/src/primitives_cache.rs b/tee-worker/core-primitives/primitives-cache/src/primitives_cache.rs new file mode 100644 index 0000000000..40bc516f51 --- /dev/null +++ b/tee-worker/core-primitives/primitives-cache/src/primitives_cache.rs @@ -0,0 +1,117 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +#[cfg(all(not(feature = "std"), feature = "sgx"))] +use std::sync::SgxRwLock as RwLock; +#[cfg(all(not(feature = "std"), feature = "sgx"))] +use std::sync::SgxRwLockReadGuard as RwLockReadGuard; +#[cfg(all(not(feature = "std"), feature = "sgx"))] +use std::sync::SgxRwLockWriteGuard as RwLockWriteGuard; + +#[cfg(feature = "std")] +use std::sync::RwLock; +#[cfg(feature = "std")] +use std::sync::RwLockReadGuard; +#[cfg(feature = "std")] +use std::sync::RwLockWriteGuard; + +use std::string::{String, ToString}; + +use crate::{ + error::{Error, Result}, + GetPrimitives, MutatePrimitives, Primitives, +}; + +/// Local primitives cache. +/// +/// Stores the primitives internally, protected by a RW lock for concurrent access. +#[derive(Default)] +pub struct PrimitivesCache { + primitives_lock: RwLock, +} + +impl PrimitivesCache { + pub fn new(primitives_lock: RwLock) -> Self { + PrimitivesCache { primitives_lock } + } +} + +impl MutatePrimitives for PrimitivesCache { + fn load_for_mutation(&self) -> Result> { + self.primitives_lock.write().map_err(|_| Error::LockPoisoning) + } +} + +impl GetPrimitives for PrimitivesCache { + fn get_primitives(&self) -> Result> { + self.primitives_lock.read().map_err(|_| Error::LockPoisoning) + } + + fn get_mu_ra_url(&self) -> Result { + let primitives_lock = self.primitives_lock.read().map_err(|_| Error::LockPoisoning)?; + Ok(primitives_lock.mu_ra_url().to_string()) + } + + fn get_untrusted_worker_url(&self) -> Result { + let primitives_lock = self.primitives_lock.read().map_err(|_| Error::LockPoisoning)?; + Ok(primitives_lock.untrusted_worker_url().to_string()) + } +} + +#[cfg(test)] +pub mod tests { + use super::*; + use std::{sync::Arc, thread}; + + #[test] + pub fn set_primitives_works() { + let cache = PrimitivesCache::default(); + let mut lock = cache.load_for_mutation().unwrap(); + let mu_ra_url = "hello".to_string(); + let untrusted_url = "world".to_string(); + let primitives = Primitives::new(mu_ra_url, untrusted_url); + *lock = primitives.clone(); + std::mem::drop(lock); + assert_eq!(primitives, *cache.get_primitives().unwrap()); + } + + #[test] + pub fn concurrent_read_access_blocks_until_write_is_done() { + let cache = Arc::new(PrimitivesCache::default()); + let mu_ra_url = "hello".to_string(); + let untrusted_url = "world".to_string(); + let primitives = Primitives::new(mu_ra_url, untrusted_url); + + let mut write_lock = cache.load_for_mutation().unwrap(); + + // Spawn a new thread that reads the primitives. + // This thread should be blocked until the write lock is released, i.e. until + // the new primitves are written. We can verify this, by trying to read the primitives variable + // that will be inserted further down below. + let new_thread_cache = cache.clone(); + let primitives_one = primitives.clone(); + let join_handle = thread::spawn(move || { + let read = new_thread_cache.get_primitives().unwrap(); + assert_eq!(primitives_one, *read); + }); + + *write_lock = primitives; + std::mem::drop(write_lock); + + join_handle.join().unwrap(); + } +} diff --git a/tee-worker/core-primitives/rpc/Cargo.toml b/tee-worker/core-primitives/rpc/Cargo.toml new file mode 100644 index 0000000000..ec26a03f43 --- /dev/null +++ b/tee-worker/core-primitives/rpc/Cargo.toml @@ -0,0 +1,26 @@ +[package] +authors = ["Integritee AG "] +edition = "2021" +name = "itp-rpc" +version = "0.9.0" + +[dependencies] +codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } +itp-types = { default-features = false, path = "../types" } +serde = { version = "1.0", default-features = false, features = ["alloc", "derive"] } +serde_json = { version = "1.0", default-features = false, features = ["alloc"] } + +# sgx deps +sgx_tstd = { branch = "master", git = "https://github.com/apache/teaclave-sgx-sdk.git", optional = true } + +[features] +default = ["std"] +sgx = [ + "sgx_tstd", +] +std = [ + "codec/std", + "itp-types/std", + "serde/std", + "serde_json/std", +] diff --git a/tee-worker/core-primitives/rpc/src/lib.rs b/tee-worker/core-primitives/rpc/src/lib.rs new file mode 100644 index 0000000000..58ca257ee2 --- /dev/null +++ b/tee-worker/core-primitives/rpc/src/lib.rs @@ -0,0 +1,73 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +#![cfg_attr(not(feature = "std"), no_std)] + +#[cfg(all(feature = "std", feature = "sgx"))] +compile_error!("feature \"std\" and feature \"sgx\" cannot be enabled at the same time"); + +#[cfg(all(not(feature = "std"), feature = "sgx"))] +extern crate sgx_tstd as std; + +use codec::{Decode, Encode}; +use itp_types::DirectRequestStatus; +use serde::{Deserialize, Serialize}; +use std::{borrow::ToOwned, string::String, vec::Vec}; + +#[derive(Encode, Decode, Debug)] +pub struct RpcReturnValue { + pub value: Vec, + pub do_watch: bool, + pub status: DirectRequestStatus, +} +impl RpcReturnValue { + pub fn new(val: Vec, watch: bool, status: DirectRequestStatus) -> Self { + Self { value: val, do_watch: watch, status } + } + + pub fn from_error_message(error_msg: &str) -> Self { + RpcReturnValue { + value: error_msg.encode(), + do_watch: false, + status: DirectRequestStatus::Error, + } + } +} + +#[derive(Clone, Encode, Decode, Debug, Serialize, Deserialize)] +pub struct RpcResponse { + pub jsonrpc: String, + pub result: String, // hex encoded RpcReturnValue + pub id: u32, +} + +#[derive(Clone, Encode, Decode, Serialize, Deserialize)] +pub struct RpcRequest { + pub jsonrpc: String, + pub method: String, + pub params: Vec, + pub id: i32, +} + +impl RpcRequest { + pub fn compose_jsonrpc_call( + method: String, + params: Vec, + ) -> Result { + serde_json::to_string(&RpcRequest { jsonrpc: "2.0".to_owned(), method, params, id: 1 }) + } +} diff --git a/tee-worker/core-primitives/settings/Cargo.toml b/tee-worker/core-primitives/settings/Cargo.toml new file mode 100644 index 0000000000..048ba294e8 --- /dev/null +++ b/tee-worker/core-primitives/settings/Cargo.toml @@ -0,0 +1,14 @@ +[package] +authors = ["Integritee AG "] +edition = "2021" +name = "itp-settings" +version = "0.9.0" + +[dependencies] + + +[features] +offchain-worker = [] +production = [] +sidechain = [] +teeracle = [] diff --git a/tee-worker/core-primitives/settings/src/lib.rs b/tee-worker/core-primitives/settings/src/lib.rs new file mode 100644 index 0000000000..749450c43b --- /dev/null +++ b/tee-worker/core-primitives/settings/src/lib.rs @@ -0,0 +1,110 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +//! Common settings for the worker and the enclave. It is strictly `no_std` + +#![no_std] + +#[cfg(any( + all(feature = "sidechain", feature = "offchain-worker"), + all(feature = "sidechain", feature = "teeracle"), + all(feature = "teeracle", feature = "offchain-worker") +))] +compile_error!( + "feature \"sidechain\" , \"offchain-worker\" or \"teeracle\" cannot be enabled at the same time" +); + +pub mod worker_mode; + +pub mod files { + // used by worker + pub static ENCLAVE_TOKEN: &str = "enclave.token"; + pub static ENCLAVE_FILE: &str = "enclave.signed.so"; + pub static SHIELDING_KEY_FILE: &str = "enclave-shielding-pubkey.json"; + pub static SIGNING_KEY_FILE: &str = "enclave-signing-pubkey.bin"; + /// sidechain database path + pub static SIDECHAIN_STORAGE_PATH: &str = "sidechain_db"; + pub static SIDECHAIN_PURGE_INTERVAL: u64 = 7200; // purge sidechain every .. s + pub static SIDECHAIN_PURGE_LIMIT: u64 = 100; // keep the last.. sidechainblocks when purging + + // used by enclave + pub const RSA3072_SEALED_KEY_FILE: &str = "rsa3072_key_sealed.bin"; + pub const SEALED_SIGNER_SEED_FILE: &str = "ed25519_key_sealed.bin"; + pub const AES_KEY_FILE_AND_INIT_V: &str = "aes_key_sealed.bin"; + pub const LIGHT_CLIENT_DB: &str = "light_client_db.bin"; + + pub const RA_DUMP_CERT_DER_FILE: &str = "ra_dump_cert.der"; + + // used by worker and enclave + pub const SHARDS_PATH: &str = "shards"; + pub const ENCRYPTED_STATE_FILE: &str = "state.bin"; + pub const LAST_SLOT_BIN: &str = "last_slot.bin"; + + #[cfg(feature = "production")] + pub static RA_SPID_FILE: &str = "spid_production.txt"; + #[cfg(feature = "production")] + pub static RA_API_KEY_FILE: &str = "key_production.txt"; + + #[cfg(not(feature = "production"))] + pub static RA_SPID_FILE: &str = "spid.txt"; + #[cfg(not(feature = "production"))] + pub static RA_API_KEY_FILE: &str = "key.txt"; + + pub const SPID_MIN_LENGTH: usize = 32; + pub const STATE_SNAPSHOTS_CACHE_SIZE: usize = 4; +} + +/// Settings concerning the worker +pub mod worker { + // the maximum size of any extrinsic that the enclave will ever generate in B + pub const EXTRINSIC_MAX_SIZE: usize = 4196; + // the maximum size of the header + pub const HEADER_MAX_SIZE: usize = 200; + // maximum size of shielding key + pub const SHIELDING_KEY_SIZE: usize = 8192; + // maximum size of signing key + pub const SIGNING_KEY_SIZE: usize = 32; + // size of the MR enclave + pub const MR_ENCLAVE_SIZE: usize = 32; + // Factors to tune the initial amount of enclave funding: + // Should be set to a value that ensures that the enclave can register itself + // and the worker can run for a certain time. Only for development. + pub const EXISTENTIAL_DEPOSIT_FACTOR_FOR_INIT_FUNDS: u128 = 1_000; + // Should be set to a value that ensures that the enclave can register itself + // and that the worker can start. + pub const REGISTERING_FEE_FACTOR_FOR_INIT_FUNDS: u128 = 10; + // Should be set to a value that ensures that at least 2 sidechain blocks are finalized per + // parentchain block. + pub const BLOCK_NUMBER_FINALIZATION_DIFF: u64 = 20; +} + +pub mod sidechain { + use core::time::Duration; + + pub static SLOT_DURATION: Duration = Duration::from_millis(6000); +} + +/// Settings concerning the enclave +pub mod enclave {} + +/// Settings for the Teeracle +#[cfg(feature = "teeracle")] +pub mod teeracle { + use core::time::Duration; + // Send extrinsic to update market exchange rate on the parentchain once per day + pub static DEFAULT_MARKET_DATA_UPDATE_INTERVAL: Duration = Duration::from_secs(86400); +} diff --git a/tee-worker/core-primitives/settings/src/worker_mode.rs b/tee-worker/core-primitives/settings/src/worker_mode.rs new file mode 100644 index 0000000000..7eef1144fa --- /dev/null +++ b/tee-worker/core-primitives/settings/src/worker_mode.rs @@ -0,0 +1,59 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +#[derive(Eq, PartialEq, Debug, Clone)] +pub enum WorkerMode { + OffChainWorker, + Sidechain, + Teeracle, +} + +pub trait ProvideWorkerMode { + fn worker_mode() -> WorkerMode; +} + +#[derive(Default, Copy, Clone)] +pub struct WorkerModeProvider; + +#[cfg(feature = "offchain-worker")] +impl ProvideWorkerMode for WorkerModeProvider { + fn worker_mode() -> WorkerMode { + WorkerMode::OffChainWorker + } +} + +#[cfg(feature = "teeracle")] +impl ProvideWorkerMode for WorkerModeProvider { + fn worker_mode() -> WorkerMode { + WorkerMode::Teeracle + } +} + +#[cfg(feature = "sidechain")] +impl ProvideWorkerMode for WorkerModeProvider { + fn worker_mode() -> WorkerMode { + WorkerMode::Sidechain + } +} + +// Default to `Sidechain` worker mode when no cargo features are set. +#[cfg(not(any(feature = "sidechain", feature = "teeracle", feature = "offchain-worker")))] +impl ProvideWorkerMode for WorkerModeProvider { + fn worker_mode() -> WorkerMode { + WorkerMode::Sidechain + } +} diff --git a/tee-worker/core-primitives/sgx-runtime-primitives/Cargo.toml b/tee-worker/core-primitives/sgx-runtime-primitives/Cargo.toml new file mode 100644 index 0000000000..b59e028349 --- /dev/null +++ b/tee-worker/core-primitives/sgx-runtime-primitives/Cargo.toml @@ -0,0 +1,22 @@ +[package] +authors = ["Integritee AG "] +edition = "2021" +name = "itp-sgx-runtime-primitives" +version = "0.9.0" + +[dependencies] + +# Substrate dependencies +frame-system = { default-features = false, git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.29" } +pallet-balances = { default-features = false, git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.29" } +sp-core = { default-features = false, git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.29" } +sp-runtime = { default-features = false, git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.29" } + +[features] +default = ["std"] +std = [ + "frame-system/std", + "pallet-balances/std", + "sp-core/std", + "sp-runtime/std", +] diff --git a/tee-worker/core-primitives/sgx-runtime-primitives/src/constants.rs b/tee-worker/core-primitives/sgx-runtime-primitives/src/constants.rs new file mode 100644 index 0000000000..75eac384f1 --- /dev/null +++ b/tee-worker/core-primitives/sgx-runtime-primitives/src/constants.rs @@ -0,0 +1,29 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +use crate::types::{BlockNumber, Moment}; + +pub const ONE_DAY: Moment = 86_400_000; + +pub const MILLISECS_PER_BLOCK: u64 = 6000; + +pub const SLOT_DURATION: u64 = MILLISECS_PER_BLOCK; + +// Time is measured by number of blocks. +pub const MINUTES: BlockNumber = 60_000 / (MILLISECS_PER_BLOCK as BlockNumber); +pub const HOURS: BlockNumber = MINUTES * 60; +pub const DAYS: BlockNumber = HOURS * 24; diff --git a/tee-worker/core-primitives/sgx-runtime-primitives/src/lib.rs b/tee-worker/core-primitives/sgx-runtime-primitives/src/lib.rs new file mode 100644 index 0000000000..74007111ba --- /dev/null +++ b/tee-worker/core-primitives/sgx-runtime-primitives/src/lib.rs @@ -0,0 +1,21 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +#![cfg_attr(not(feature = "std"), no_std)] + +pub mod constants; +pub mod types; diff --git a/tee-worker/core-primitives/sgx-runtime-primitives/src/types.rs b/tee-worker/core-primitives/sgx-runtime-primitives/src/types.rs new file mode 100644 index 0000000000..035ae982b8 --- /dev/null +++ b/tee-worker/core-primitives/sgx-runtime-primitives/src/types.rs @@ -0,0 +1,66 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +use sp_runtime::{ + generic::{self, Block as BlockG, SignedBlock as SignedBlockG}, + traits::{BlakeTwo256, IdentifyAccount, Verify}, + MultiSignature, OpaqueExtrinsic, +}; + +/// The address format for describing accounts. +pub type Address = sp_runtime::MultiAddress; +/// Block header type as expected by this sgx-runtime. +pub type Header = generic::Header; + +/// An index to a block. +pub type BlockNumber = u32; +pub type SidechainBlockNumber = u64; +pub type SidechainTimestamp = u64; + +/// Alias to 512-bit hash when used in the context of a transaction signature on the chain. +pub type Signature = MultiSignature; + +/// Some way of identifying an account on the chain. We intentionally make it equivalent +/// to the public key of our transaction signing scheme. +pub type AccountId = <::Signer as IdentifyAccount>::AccountId; + +pub type AccountData = pallet_balances::AccountData; +pub type AccountInfo = frame_system::AccountInfo; + +/// The type for looking up accounts. We don't expect more than 4 billion of them, but you +/// never know... +pub type AccountIndex = u32; + +/// Balance of an account. +pub type Balance = u128; + +/// Index of a transaction in the chain. +pub type Index = u32; + +/// A hash of some data used by the chain. +pub type Hash = sp_core::H256; + +/// Digest item type. +pub type DigestItem = generic::DigestItem; + +/// A type to hold UTC unix epoch [ms] +pub type Moment = u64; + +pub type Block = BlockG; +pub type SignedBlock = SignedBlockG; +pub type BlockHash = sp_core::H256; +pub type ShardIdentifier = sp_core::H256; diff --git a/tee-worker/core-primitives/sgx/crypto/Cargo.toml b/tee-worker/core-primitives/sgx/crypto/Cargo.toml new file mode 100644 index 0000000000..1f4ee363a7 --- /dev/null +++ b/tee-worker/core-primitives/sgx/crypto/Cargo.toml @@ -0,0 +1,51 @@ +[package] +edition = "2021" +name = "itp-sgx-crypto" +version = "0.9.0" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +aes = { version = "0.6.0" } +codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } +derive_more = { version = "0.99.5" } +log = { version = "0.4", default-features = false } +ofb = { version = "0.4.0" } +serde = { version = "1.0", default-features = false, features = ["alloc"], optional = true } +serde_json = { version = "1.0", default-features = false, features = ["alloc"], optional = true } + +# sgx deps +serde-sgx = { package = "serde", tag = "sgx_1.1.3", git = "https://github.com/mesalock-linux/serde-sgx", optional = true } +serde_json-sgx = { package = "serde_json", tag = "sgx_1.1.3", git = "https://github.com/mesalock-linux/serde-json-sgx", optional = true } +sgx-crypto-helper = { branch = "master", git = "https://github.com/apache/teaclave-sgx-sdk.git", package = "sgx_crypto_helper", default-features = false } +sgx_rand = { branch = "master", git = "https://github.com/apache/teaclave-sgx-sdk.git", optional = true } +sgx_tstd = { branch = "master", git = "https://github.com/apache/teaclave-sgx-sdk.git", optional = true } +sgx_types = { branch = "master", git = "https://github.com/apache/teaclave-sgx-sdk.git" } + +# substrate deps +sp-core = { default-features = false, git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.29" } + +# local deps +itp-settings = { path = "../../settings" } +itp-sgx-io = { path = "../io", default-features = false } + +[features] +default = ["std"] +mocks = [] +sgx = [ + "sgx-crypto-helper/mesalock_sgx", + "sgx_tstd", + "sgx_rand", + "itp-sgx-io/sgx", + "serde_json-sgx", + "serde-sgx", +] +std = [ + "codec/std", + "log/std", + "itp-sgx-io/std", + "sp-core/std", + "serde/std", + "serde_json/std", + "sgx-crypto-helper/default", +] diff --git a/tee-worker/core-primitives/sgx/crypto/src/aes.rs b/tee-worker/core-primitives/sgx/crypto/src/aes.rs new file mode 100644 index 0000000000..d63bed35a2 --- /dev/null +++ b/tee-worker/core-primitives/sgx/crypto/src/aes.rs @@ -0,0 +1,130 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +use crate::{ + error::{Error, Result}, + traits::StateCrypto, +}; +use aes::Aes128; +use codec::{Decode, Encode}; +use derive_more::Display; +use ofb::{ + cipher::{NewStreamCipher, SyncStreamCipher}, + Ofb, +}; +use std::convert::{TryFrom, TryInto}; + +type AesOfb = Ofb; + +#[derive(Debug, Default, Encode, Decode, Clone, Copy, PartialEq, Eq)] +pub struct Aes { + pub key: [u8; 16], + pub init_vec: [u8; 16], +} + +impl Aes { + pub fn new(key: [u8; 16], init_vec: [u8; 16]) -> Self { + Self { key, init_vec } + } +} + +#[derive(Copy, Clone, Debug, Display)] +pub struct AesSeal; + +impl StateCrypto for Aes { + type Error = Error; + + fn encrypt(&self, data: &mut [u8]) -> Result<()> { + de_or_encrypt(self, data) + } + + fn decrypt(&self, data: &mut [u8]) -> Result<()> { + de_or_encrypt(self, data) + } +} + +impl TryFrom<&Aes> for AesOfb { + type Error = Error; + + fn try_from(aes: &Aes) -> std::result::Result { + AesOfb::new_var(&aes.key, &aes.init_vec).map_err(|_| Error::InvalidNonceKeyLength) + } +} + +/// If AES acts on the encrypted data it decrypts and vice versa +pub fn de_or_encrypt(aes: &Aes, data: &mut [u8]) -> Result<()> { + aes.try_into().map(|mut ofb: AesOfb| ofb.apply_keystream(data)) +} + +#[cfg(feature = "sgx")] +pub use sgx::*; + +#[cfg(feature = "sgx")] +pub mod sgx { + + use super::*; + use itp_settings::files::AES_KEY_FILE_AND_INIT_V; + use itp_sgx_io::{seal, unseal, SealedIO, StaticSealedIO}; + use log::info; + use sgx_rand::{Rng, StdRng}; + use std::sgxfs::SgxFile; + + impl StaticSealedIO for AesSeal { + type Error = Error; + type Unsealed = Aes; + + fn unseal_from_static_file() -> Result { + Ok(unseal(AES_KEY_FILE_AND_INIT_V).map(|b| Decode::decode(&mut b.as_slice()))??) + } + + fn seal_to_static_file(unsealed: &Self::Unsealed) -> Result<()> { + Ok(unsealed.using_encoded(|bytes| seal(bytes, AES_KEY_FILE_AND_INIT_V))?) + } + } + + impl SealedIO for AesSeal { + type Error = Error; + type Unsealed = Aes; + + fn unseal(&self) -> Result { + Self::unseal_from_static_file() + } + + fn seal(&self, unsealed: &Self::Unsealed) -> Result<()> { + Self::seal_to_static_file(&unsealed) + } + } + + pub fn create_sealed_if_absent() -> Result<()> { + if SgxFile::open(AES_KEY_FILE_AND_INIT_V).is_err() { + info!("[Enclave] Keyfile not found, creating new! {}", AES_KEY_FILE_AND_INIT_V); + return create_sealed() + } + Ok(()) + } + + pub fn create_sealed() -> Result<()> { + let mut key = [0u8; 16]; + let mut iv = [0u8; 16]; + + let mut rand = StdRng::new()?; + + rand.fill_bytes(&mut key); + rand.fill_bytes(&mut iv); + AesSeal::seal_to_static_file(&Aes::new(key, iv)) + } +} diff --git a/tee-worker/core-primitives/sgx/crypto/src/ed25519.rs b/tee-worker/core-primitives/sgx/crypto/src/ed25519.rs new file mode 100644 index 0000000000..07082d38ce --- /dev/null +++ b/tee-worker/core-primitives/sgx/crypto/src/ed25519.rs @@ -0,0 +1,88 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +use derive_more::Display; + +#[derive(Copy, Clone, Debug, Display)] +pub struct Ed25519Seal; + +#[cfg(feature = "sgx")] +pub use sgx::*; + +#[cfg(feature = "sgx")] +pub mod sgx { + + use super::*; + use crate::error::{Error, Result}; + use codec::Encode; + use itp_settings::files::SEALED_SIGNER_SEED_FILE; + use itp_sgx_io::{seal, unseal, SealedIO, StaticSealedIO}; + use log::*; + use sgx_rand::{Rng, StdRng}; + use sp_core::{crypto::Pair, ed25519}; + use std::{path::Path, sgxfs::SgxFile}; + + impl StaticSealedIO for Ed25519Seal { + type Error = Error; + type Unsealed = ed25519::Pair; + + fn unseal_from_static_file() -> Result { + let raw = unseal(SEALED_SIGNER_SEED_FILE)?; + + let key = ed25519::Pair::from_seed_slice(&raw) + .map_err(|e| Error::Other(format!("{:?}", e).into()))?; + + Ok(key.into()) + } + + fn seal_to_static_file(unsealed: &Self::Unsealed) -> Result<()> { + Ok(unsealed.seed().using_encoded(|bytes| seal(bytes, SEALED_SIGNER_SEED_FILE))?) + } + } + + impl SealedIO for Ed25519Seal { + type Error = Error; + type Unsealed = ed25519::Pair; + + fn unseal(&self) -> Result { + Self::unseal_from_static_file() + } + + fn seal(&self, unsealed: &Self::Unsealed) -> Result<()> { + Self::seal_to_static_file(unsealed) + } + } + + pub fn create_sealed_if_absent() -> Result<()> { + if SgxFile::open(SEALED_SIGNER_SEED_FILE).is_err() { + if Path::new(SEALED_SIGNER_SEED_FILE).exists() { + panic!("[Enclave] Keyfile {} exists but can't be opened. has it been written by the same enclave?", SEALED_SIGNER_SEED_FILE); + } + info!("[Enclave] Keyfile not found, creating new! {}", SEALED_SIGNER_SEED_FILE); + return create_sealed_seed() + } + Ok(()) + } + + pub fn create_sealed_seed() -> Result<()> { + let mut seed = [0u8; 32]; + let mut rand = StdRng::new()?; + rand.fill_bytes(&mut seed); + + Ok(seal(&seed, SEALED_SIGNER_SEED_FILE)?) + } +} diff --git a/tee-worker/core-primitives/sgx/crypto/src/ed25519_derivation.rs b/tee-worker/core-primitives/sgx/crypto/src/ed25519_derivation.rs new file mode 100644 index 0000000000..25e51279c7 --- /dev/null +++ b/tee-worker/core-primitives/sgx/crypto/src/ed25519_derivation.rs @@ -0,0 +1,36 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +#[cfg(all(not(feature = "std"), feature = "sgx"))] +use crate::sgx_reexport_prelude::*; + +use crate::error::Result; +use sgx_crypto_helper::rsa3072::Rsa3072KeyPair; +use sp_core::{blake2_256, ed25519::Pair as Ed25519Pair, Pair}; + +/// Trait to derive an Ed25519 key pair. +pub trait DeriveEd25519 { + fn derive_ed25519(&self) -> Result; +} + +impl DeriveEd25519 for Rsa3072KeyPair { + fn derive_ed25519(&self) -> Result { + let encoded_key = serde_json::to_vec(self)?; + let seed = blake2_256(&encoded_key); + Ok(Ed25519Pair::from_seed(&seed)) + } +} diff --git a/tee-worker/core-primitives/sgx/crypto/src/error.rs b/tee-worker/core-primitives/sgx/crypto/src/error.rs new file mode 100644 index 0000000000..4fa619d136 --- /dev/null +++ b/tee-worker/core-primitives/sgx/crypto/src/error.rs @@ -0,0 +1,43 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +#[cfg(all(not(feature = "std"), feature = "sgx"))] +use crate::sgx_reexport_prelude::*; + +use derive_more::{Display, From}; +use sgx_types::sgx_status_t; +use std::prelude::v1::Box; + +#[derive(Debug, Display, From)] +pub enum Error { + IO(std::io::Error), + InvalidNonceKeyLength, + Codec(codec::Error), + Serialization(serde_json::Error), + LockPoisoning, + Other(Box), +} + +pub type Result = core::result::Result; + +impl From for sgx_status_t { + /// return sgx_status for top level enclave functions + fn from(error: Error) -> sgx_status_t { + log::warn!("Transform non-sgx-error into `SGX_ERROR_UNEXPECTED`: {:?}", error); + sgx_status_t::SGX_ERROR_UNEXPECTED + } +} diff --git a/tee-worker/core-primitives/sgx/crypto/src/key_repository.rs b/tee-worker/core-primitives/sgx/crypto/src/key_repository.rs new file mode 100644 index 0000000000..626321d0f8 --- /dev/null +++ b/tee-worker/core-primitives/sgx/crypto/src/key_repository.rs @@ -0,0 +1,100 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +#[cfg(feature = "sgx")] +use std::sync::SgxRwLock as RwLock; + +#[cfg(feature = "std")] +use std::sync::RwLock; + +use crate::error::{Error, Result}; +use itp_sgx_io::SealedIO; +use std::sync::Arc; + +/// Access a cryptographic key. +pub trait AccessKey { + type KeyType; + + fn retrieve_key(&self) -> Result; +} + +/// Mutate a cryptographic key. +pub trait MutateKey { + fn update_key(&self, key: KeyType) -> Result<()>; +} + +/// Repository implementation. Stores a cryptographic key in-memory and in a file backed. +/// Uses the SealedIO trait for the file backend. +pub struct KeyRepository { + key_lock: RwLock, + sealed_io: Arc, +} + +impl KeyRepository { + pub fn new(key: KeyType, sealed_io: Arc) -> Self { + KeyRepository { key_lock: RwLock::new(key), sealed_io } + } +} + +impl AccessKey for KeyRepository +where + KeyType: Clone, + SealedIo: SealedIO, +{ + type KeyType = KeyType; + + fn retrieve_key(&self) -> Result { + self.key_lock.read().map_err(|_| Error::LockPoisoning).map(|l| l.clone()) + } +} + +impl MutateKey for KeyRepository +where + KeyType: Clone, + SealedIo: SealedIO, +{ + fn update_key(&self, key: KeyType) -> Result<()> { + let mut key_lock = self.key_lock.write().map_err(|_| Error::LockPoisoning)?; + + self.sealed_io.seal(&key)?; + *key_lock = self.sealed_io.unseal()?; + + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::{aes::Aes, mocks::AesSealMock}; + + type TestKeyRepository = KeyRepository; + + #[test] + fn update_and_retrieve_key_works() { + let seal_mock = Arc::new(AesSealMock::default()); + let key_repository = TestKeyRepository::new(seal_mock.unseal().unwrap(), seal_mock.clone()); + + assert_eq!(seal_mock.unseal().unwrap(), key_repository.retrieve_key().unwrap()); + + let updated_key = Aes::new([2u8; 16], [0u8; 16]); + key_repository.update_key(updated_key).unwrap(); + + assert_eq!(updated_key, key_repository.retrieve_key().unwrap()); + assert_eq!(updated_key, seal_mock.unseal().unwrap()); + } +} diff --git a/tee-worker/core-primitives/sgx/crypto/src/lib.rs b/tee-worker/core-primitives/sgx/crypto/src/lib.rs new file mode 100644 index 0000000000..2507bc8916 --- /dev/null +++ b/tee-worker/core-primitives/sgx/crypto/src/lib.rs @@ -0,0 +1,49 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +//! All the different crypto schemes that we use in sgx + +#![cfg_attr(not(feature = "std"), no_std)] + +#[cfg(all(feature = "std", feature = "sgx"))] +compile_error!("feature \"std\" and feature \"sgx\" cannot be enabled at the same time"); + +#[cfg(all(not(feature = "std"), feature = "sgx"))] +#[macro_use] +extern crate sgx_tstd as std; + +// re-export module to properly feature gate sgx and regular std environment +#[cfg(all(not(feature = "std"), feature = "sgx"))] +pub mod sgx_reexport_prelude { + pub use serde_json_sgx as serde_json; + pub use serde_sgx as serde; +} + +pub mod aes; +pub mod ed25519; +pub mod ed25519_derivation; +pub mod error; +pub mod key_repository; +pub mod rsa3072; +pub mod traits; + +pub use self::{aes::*, ed25519::*, rsa3072::*}; +pub use error::*; +pub use traits::*; + +#[cfg(feature = "mocks")] +pub mod mocks; diff --git a/tee-worker/core-primitives/sgx/crypto/src/mocks.rs b/tee-worker/core-primitives/sgx/crypto/src/mocks.rs new file mode 100644 index 0000000000..0e199378fd --- /dev/null +++ b/tee-worker/core-primitives/sgx/crypto/src/mocks.rs @@ -0,0 +1,118 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +#[cfg(feature = "sgx")] +use std::sync::SgxRwLock as RwLock; + +#[cfg(feature = "std")] +use std::sync::RwLock; + +use crate::{ + aes::Aes, + error::{Error, Result}, + key_repository::{AccessKey, MutateKey}, +}; +use itp_sgx_io::{SealedIO, StaticSealedIO}; +use sgx_crypto_helper::rsa3072::Rsa3072KeyPair; + +#[derive(Default)] +pub struct KeyRepositoryMock +where + KeyType: Clone + Default, +{ + key: RwLock, +} + +impl KeyRepositoryMock +where + KeyType: Clone + Default, +{ + pub fn new(key: KeyType) -> Self { + KeyRepositoryMock { key: RwLock::new(key) } + } +} + +impl AccessKey for KeyRepositoryMock +where + KeyType: Clone + Default, +{ + type KeyType = KeyType; + + fn retrieve_key(&self) -> Result { + Ok(self.key.read().unwrap().clone()) + } +} + +impl MutateKey for KeyRepositoryMock +where + KeyType: Clone + Default, +{ + fn update_key(&self, key: KeyType) -> Result<()> { + let mut lock = self.key.write().unwrap(); + *lock = key; + Ok(()) + } +} + +#[derive(Default)] +pub struct AesSealMock { + aes: RwLock, +} + +impl StaticSealedIO for AesSealMock { + type Error = Error; + type Unsealed = Aes; + + fn unseal_from_static_file() -> Result { + Ok(Aes::default()) + } + + fn seal_to_static_file(_unsealed: &Self::Unsealed) -> Result<()> { + Ok(()) + } +} + +impl SealedIO for AesSealMock { + type Error = Error; + type Unsealed = Aes; + + fn unseal(&self) -> std::result::Result { + self.aes.read().map_err(|e| Error::Other(format!("{:?}", e).into())).map(|k| *k) + } + + fn seal(&self, unsealed: &Self::Unsealed) -> Result<()> { + let mut aes_lock = self.aes.write().map_err(|e| Error::Other(format!("{:?}", e).into()))?; + *aes_lock = *unsealed; + Ok(()) + } +} + +#[derive(Default)] +pub struct Rsa3072SealMock {} + +impl StaticSealedIO for Rsa3072SealMock { + type Error = Error; + type Unsealed = Rsa3072KeyPair; + + fn unseal_from_static_file() -> Result { + Ok(Rsa3072KeyPair::default()) + } + + fn seal_to_static_file(_unsealed: &Self::Unsealed) -> Result<()> { + Ok(()) + } +} diff --git a/tee-worker/core-primitives/sgx/crypto/src/rsa3072.rs b/tee-worker/core-primitives/sgx/crypto/src/rsa3072.rs new file mode 100644 index 0000000000..feb6d7204c --- /dev/null +++ b/tee-worker/core-primitives/sgx/crypto/src/rsa3072.rs @@ -0,0 +1,132 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ +#[cfg(all(not(feature = "std"), feature = "sgx"))] +use crate::sgx_reexport_prelude::*; + +use crate::{ + error::{Error, Result}, + traits::{ShieldingCryptoDecrypt, ShieldingCryptoEncrypt}, +}; +use sgx_crypto_helper::{ + rsa3072::{Rsa3072KeyPair, Rsa3072PubKey}, + RsaKeyPair, +}; +use std::vec::Vec; + +// Reexport sgx module +#[cfg(feature = "sgx")] +pub use sgx::*; + +impl ShieldingCryptoEncrypt for Rsa3072KeyPair { + type Error = Error; + + fn encrypt(&self, data: &[u8]) -> Result> { + let mut cipher_buffer = Vec::new(); + self.encrypt_buffer(data, &mut cipher_buffer) + .map_err(|e| Error::Other(format!("{:?}", e).into()))?; + Ok(cipher_buffer) + } +} + +impl ShieldingCryptoDecrypt for Rsa3072KeyPair { + type Error = Error; + + fn decrypt(&self, data: &[u8]) -> Result> { + let mut decrypted_buffer = Vec::new(); + self.decrypt_buffer(data, &mut decrypted_buffer) + .map_err(|e| Error::Other(format!("{:?}", e).into()))?; + Ok(decrypted_buffer) + } +} + +impl ShieldingCryptoEncrypt for Rsa3072PubKey { + type Error = Error; + + fn encrypt(&self, data: &[u8]) -> Result> { + let mut cipher_buffer = Vec::new(); + self.encrypt_buffer(data, &mut cipher_buffer) + .map_err(|e| Error::Other(format!("{:?}", e).into()))?; + Ok(cipher_buffer) + } +} + +#[cfg(feature = "sgx")] +pub mod sgx { + use super::*; + use derive_more::Display; + use itp_settings::files::RSA3072_SEALED_KEY_FILE; + use itp_sgx_io::{seal, unseal, SealedIO, StaticSealedIO}; + use log::*; + use std::sgxfs::SgxFile; + + impl Rsa3072Seal { + pub fn unseal_pubkey() -> Result { + let pair = Self::unseal_from_static_file()?; + let pubkey = + pair.export_pubkey().map_err(|e| Error::Other(format!("{:?}", e).into()))?; + Ok(pubkey) + } + } + + pub fn create_sealed_if_absent() -> Result<()> { + if SgxFile::open(RSA3072_SEALED_KEY_FILE).is_err() { + info!("[Enclave] Keyfile not found, creating new! {}", RSA3072_SEALED_KEY_FILE); + return create_sealed() + } + Ok(()) + } + + pub fn create_sealed() -> Result<()> { + let rsa_keypair = + Rsa3072KeyPair::new().map_err(|e| Error::Other(format!("{:?}", e).into()))?; + // println!("[Enclave] generated RSA3072 key pair. Cleartext: {}", rsa_key_json); + Rsa3072Seal::seal_to_static_file(&rsa_keypair) + } + + #[derive(Copy, Clone, Debug, Display)] + pub struct Rsa3072Seal; + + impl StaticSealedIO for Rsa3072Seal { + type Error = Error; + type Unsealed = Rsa3072KeyPair; + fn unseal_from_static_file() -> Result { + let raw = unseal(RSA3072_SEALED_KEY_FILE)?; + let key: Rsa3072KeyPair = serde_json::from_slice(&raw) + .map_err(|e| Error::Other(format!("{:?}", e).into()))?; + Ok(key.into()) + } + + fn seal_to_static_file(unsealed: &Self::Unsealed) -> Result<()> { + let key_json = serde_json::to_vec(&unsealed) + .map_err(|e| Error::Other(format!("{:?}", e).into()))?; + Ok(seal(&key_json, RSA3072_SEALED_KEY_FILE)?) + } + } + + impl SealedIO for Rsa3072Seal { + type Error = Error; + type Unsealed = Rsa3072KeyPair; + + fn unseal(&self) -> Result { + Self::unseal_from_static_file() + } + + fn seal(&self, unsealed: &Self::Unsealed) -> Result<()> { + Self::seal_to_static_file(unsealed) + } + } +} diff --git a/tee-worker/core-primitives/sgx/crypto/src/traits.rs b/tee-worker/core-primitives/sgx/crypto/src/traits.rs new file mode 100644 index 0000000000..fde231ff33 --- /dev/null +++ b/tee-worker/core-primitives/sgx/crypto/src/traits.rs @@ -0,0 +1,35 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +//! Abstraction over the state crypto that is used in the enclave +use std::{fmt::Debug, vec::Vec}; + +pub trait StateCrypto { + type Error: Debug; + fn encrypt(&self, data: &mut [u8]) -> Result<(), Self::Error>; + fn decrypt(&self, data: &mut [u8]) -> Result<(), Self::Error>; +} + +pub trait ShieldingCryptoEncrypt { + type Error: Debug; + fn encrypt(&self, data: &[u8]) -> Result, Self::Error>; +} + +pub trait ShieldingCryptoDecrypt { + type Error: Debug; + fn decrypt(&self, data: &[u8]) -> Result, Self::Error>; +} diff --git a/tee-worker/core-primitives/sgx/io/Cargo.toml b/tee-worker/core-primitives/sgx/io/Cargo.toml new file mode 100644 index 0000000000..eb2dbe372c --- /dev/null +++ b/tee-worker/core-primitives/sgx/io/Cargo.toml @@ -0,0 +1,18 @@ +[package] +edition = "2021" +name = "itp-sgx-io" +version = "0.8.0" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] + +# sgx deps +sgx_tstd = { branch = "master", git = "https://github.com/apache/teaclave-sgx-sdk.git", optional = true } + +[features] +default = ["std"] +sgx = [ + "sgx_tstd", +] +std = [] diff --git a/tee-worker/core-primitives/sgx/io/src/lib.rs b/tee-worker/core-primitives/sgx/io/src/lib.rs new file mode 100644 index 0000000000..4f6d4eaa35 --- /dev/null +++ b/tee-worker/core-primitives/sgx/io/src/lib.rs @@ -0,0 +1,94 @@ +//! SGX file IO abstractions + +#![cfg_attr(not(feature = "std"), no_std)] + +#[cfg(all(feature = "std", feature = "sgx"))] +compile_error!("feature \"std\" and feature \"sgx\" cannot be enabled at the same time"); + +#[cfg(all(not(feature = "std"), feature = "sgx"))] +extern crate sgx_tstd as std; + +use std::{ + convert::AsRef, + fs, + io::{Read, Result as IOResult, Write}, + path::Path, + string::String, + vec::Vec, +}; + +#[cfg(feature = "sgx")] +pub use sgx::*; + +/// Abstraction around IO that is supposed to use the `std::io::File` +pub trait IO: Sized { + type Error: From + std::fmt::Debug + 'static; + + fn read() -> Result; + fn write(&self) -> Result<(), Self::Error>; +} + +/// Abstraction around IO that is supposed to use `SgxFile`. We expose it also in `std` to +/// be able to put it as trait bounds in `std` and use it in tests. +/// +/// This is the static method (or associated function) version, should be made obsolete over time, +/// since it has state, but hides it in a global state. Makes it difficult to mock. +pub trait StaticSealedIO: Sized { + type Error: From + std::fmt::Debug + 'static; + + /// Type that is unsealed. + type Unsealed; + + fn unseal_from_static_file() -> Result; + fn seal_to_static_file(unsealed: &Self::Unsealed) -> Result<(), Self::Error>; +} + +/// Abstraction around IO that is supposed to use `SgxFile`. We expose it also in `std` to +/// be able to put it as trait bounds in `std` and use it in tests. +/// +pub trait SealedIO: Sized { + type Error: From + std::fmt::Debug + 'static; + + /// Type that is unsealed. + type Unsealed; + + fn unseal(&self) -> Result; + fn seal(&self, unsealed: &Self::Unsealed) -> Result<(), Self::Error>; +} + +pub fn read>(path: P) -> IOResult> { + let mut buf = Vec::new(); + fs::File::open(path).map(|mut f| f.read_to_end(&mut buf))??; + Ok(buf) +} + +pub fn write>(bytes: &[u8], path: P) -> IOResult<()> { + fs::File::create(path).map(|mut f| f.write_all(bytes))? +} + +pub fn read_to_string>(filepath: P) -> IOResult { + let mut contents = String::new(); + fs::File::open(filepath).map(|mut f| f.read_to_string(&mut contents))??; + Ok(contents) +} + +#[cfg(feature = "sgx")] +mod sgx { + use std::{ + convert::AsRef, + io::{Read, Result, Write}, + path::Path, + sgxfs::SgxFile, + vec::Vec, + }; + + pub fn unseal>(path: P) -> Result> { + let mut buf = Vec::new(); + SgxFile::open(path).map(|mut f| f.read_to_end(&mut buf))??; + Ok(buf) + } + + pub fn seal>(bytes: &[u8], path: P) -> Result<()> { + SgxFile::create(path).map(|mut f| f.write_all(bytes))? + } +} diff --git a/tee-worker/core-primitives/stf-executor/Cargo.toml b/tee-worker/core-primitives/stf-executor/Cargo.toml new file mode 100644 index 0000000000..5b58e7862b --- /dev/null +++ b/tee-worker/core-primitives/stf-executor/Cargo.toml @@ -0,0 +1,99 @@ +[package] +authors = ["Integritee AG "] +edition = "2021" +name = "itp-stf-executor" +version = "0.9.0" + +[dependencies] +# sgx dependencies +sgx-crypto-helper = { branch = "master", git = "https://github.com/apache/teaclave-sgx-sdk.git", package = "sgx_crypto_helper", default-features = false, optional = true } +sgx_tstd = { branch = "master", git = "https://github.com/apache/teaclave-sgx-sdk.git", optional = true, features = ["untrusted_time"] } +sgx_types = { branch = "master", git = "https://github.com/apache/teaclave-sgx-sdk.git" } + +# local dependencies +ita-sgx-runtime = { path = "../../app-libs/sgx-runtime", default-features = false } +ita-stf = { path = "../../app-libs/stf", default-features = false } +itp-node-api = { path = "../node-api", default-features = false } +itp-ocall-api = { path = "../ocall-api", default-features = false } +itp-sgx-crypto = { path = "../sgx/crypto", default-features = false } +itp-sgx-externalities = { default-features = false, path = "../substrate-sgx/externalities" } +itp-stf-interface = { path = "../stf-interface", default-features = false } +itp-stf-state-handler = { path = "../stf-state-handler", default-features = false } +itp-stf-state-observer = { path = "../stf-state-observer", default-features = false } +itp-storage = { path = "../storage", default-features = false } +itp-time-utils = { path = "../time-utils", default-features = false } +itp-top-pool-author = { path = "../top-pool-author", default-features = false } +itp-types = { path = "../types", default-features = false } + +# scs +substrate-api-client = { default-features = false, git = "https://github.com/scs/substrate-api-client.git", branch = "polkadot-v0.9.29" } + +# sgx enabled external libraries +thiserror_sgx = { optional = true, package = "thiserror", git = "https://github.com/mesalock-linux/thiserror-sgx", tag = "sgx_1.1.3" } + +# std compatible external libraries (make sure these versions match with the sgx-enabled ones above) +thiserror = { version = "1.0", optional = true } + +# no-std dependencies +codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } +log = { version = "0.4", default-features = false } + +# substrate dependencies +sp-core = { default-features = false, features = ["full_crypto"], git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.29" } +sp-runtime = { default-features = false, git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.29" } + +# dev dependencies +itc-parentchain-test = { path = "../../core/parentchain/test", optional = true, default-features = false } +itp-test = { path = "../test", default-features = false, optional = true } + +[dev-dependencies] +itp-stf-interface = { path = "../stf-interface", features = ["mocks"] } +itp-stf-state-observer = { path = "../stf-state-observer", features = ["mocks"] } + +[features] +default = ["std"] +mocks = [] +sgx = [ + "sgx_tstd", + "ita-stf/sgx", + "itp-node-api/sgx", + "itp-sgx-crypto/sgx", + "itp-sgx-externalities/sgx", + "itp-stf-state-handler/sgx", + "itp-stf-state-observer/sgx", + "itp-top-pool-author/sgx", + "itp-storage/sgx", + "itp-time-utils/sgx", + "thiserror_sgx", +] +std = [ + # local + "ita-stf/std", + "ita-sgx-runtime/std", + "itp-node-api/std", + "itp-ocall-api/std", + "itp-sgx-crypto/std", + "itp-sgx-externalities/std", + "itp-stf-interface/std", + "itp-stf-state-handler/std", + "itp-stf-state-observer/std", + "itp-top-pool-author/std", + "itp-storage/std", + "itp-types/std", + "itp-time-utils/std", + # crates.io + "log/std", + "codec/std", + # scs + "substrate-api-client/std", + # substrate + "sp-core/std", + "sp-runtime/std", + "thiserror", +] +test = [ + "itc-parentchain-test", + "itp-node-api/mocks", + "itp-test", + "sgx-crypto-helper", +] diff --git a/tee-worker/core-primitives/stf-executor/src/enclave_signer.rs b/tee-worker/core-primitives/stf-executor/src/enclave_signer.rs new file mode 100644 index 0000000000..4e5886cfbe --- /dev/null +++ b/tee-worker/core-primitives/stf-executor/src/enclave_signer.rs @@ -0,0 +1,134 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +use crate::{ + error::{Error, Result}, + traits::StfEnclaveSigning, + H256, +}; +use core::marker::PhantomData; +use ita_sgx_runtime::Index; +use ita_stf::{AccountId, KeyPair, TrustedCall, TrustedCallSigned, TrustedOperation}; +use itp_ocall_api::EnclaveAttestationOCallApi; +use itp_sgx_crypto::{ed25519_derivation::DeriveEd25519, key_repository::AccessKey}; +use itp_sgx_externalities::SgxExternalitiesTrait; +use itp_stf_interface::system_pallet::SystemPalletAccountInterface; +use itp_stf_state_observer::traits::ObserveState; +use itp_top_pool_author::traits::AuthorApi; +use itp_types::ShardIdentifier; +use sp_core::{ed25519::Pair as Ed25519Pair, Pair}; +use std::{boxed::Box, sync::Arc}; + +pub struct StfEnclaveSigner { + state_observer: Arc, + ocall_api: Arc, + top_pool_author: Arc, + shielding_key_repo: Arc, + _phantom: PhantomData, +} + +impl + StfEnclaveSigner +where + OCallApi: EnclaveAttestationOCallApi, + StateObserver: ObserveState, + StateObserver::StateType: SgxExternalitiesTrait, + ShieldingKeyRepository: AccessKey, + ::KeyType: DeriveEd25519, + Stf: SystemPalletAccountInterface, + Stf::Index: Into, + Author: AuthorApi + Send + Sync + 'static, +{ + pub fn new( + state_observer: Arc, + ocall_api: Arc, + shielding_key_repo: Arc, + top_pool_author: Arc, + ) -> Self { + Self { + state_observer, + ocall_api, + shielding_key_repo, + _phantom: Default::default(), + top_pool_author, + } + } + + fn get_enclave_account_nonce(&self, shard: &ShardIdentifier) -> Result { + let enclave_account = self.get_enclave_account()?; + let nonce = self + .state_observer + .observe_state(shard, move |state| Stf::get_account_nonce(state, &enclave_account))?; + + Ok(nonce) + } + + fn get_enclave_call_signing_key(&self) -> Result { + let shielding_key = self.shielding_key_repo.retrieve_key()?; + shielding_key.derive_ed25519().map_err(|e| e.into()) + } +} + +impl StfEnclaveSigning + for StfEnclaveSigner +where + OCallApi: EnclaveAttestationOCallApi, + StateObserver: ObserveState, + StateObserver::StateType: SgxExternalitiesTrait, + ShieldingKeyRepository: AccessKey, + ::KeyType: DeriveEd25519, + Stf: SystemPalletAccountInterface, + Stf::Index: Into, + Author: AuthorApi + Send + Sync + 'static, +{ + fn get_enclave_account(&self) -> Result { + let enclave_call_signing_key = self.get_enclave_call_signing_key()?; + Ok(enclave_call_signing_key.public().into()) + } + + fn sign_call_with_self( + &self, + trusted_call: &TrustedCall, + shard: &ShardIdentifier, + ) -> Result { + let mr_enclave = self.ocall_api.get_mrenclave_of_self()?; + let enclave_account_id = self.get_enclave_account()?; + let enclave_call_signing_key = self.get_enclave_call_signing_key()?; + + let current_nonce = self.get_enclave_account_nonce(shard)?; + let pending_tx = self + .top_pool_author + .get_pending_trusted_calls(shard.clone()) + .iter() + .filter(|v| match v { + TrustedOperation::indirect_call(ref call) => + call.call.sender_account().eq(&enclave_account_id), + TrustedOperation::direct_call(ref call) => + call.call.sender_account().eq(&enclave_account_id), + _ => false, + }) + .count(); + let pending_tx = Index::try_from(pending_tx).map_err(|e| Error::Other(e.into()))?; + let adjust_nonce: Index = current_nonce.into() + pending_tx; + Ok(trusted_call.sign( + &KeyPair::Ed25519(Box::new(enclave_call_signing_key)), + adjust_nonce, + &mr_enclave.m, + shard, + )) + } +} diff --git a/tee-worker/core-primitives/stf-executor/src/error.rs b/tee-worker/core-primitives/stf-executor/src/error.rs new file mode 100644 index 0000000000..7a959bccf0 --- /dev/null +++ b/tee-worker/core-primitives/stf-executor/src/error.rs @@ -0,0 +1,87 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +#[cfg(all(not(feature = "std"), feature = "sgx"))] +use crate::sgx_reexport_prelude::*; + +use sgx_types::sgx_status_t; +use std::{boxed::Box, format}; + +pub type Result = core::result::Result; + +/// STF-Executor error +#[derive(Debug, thiserror::Error)] +pub enum Error { + #[error("Trusted operation has invalid signature")] + OperationHasInvalidSignature, + #[error("Invalid or unsupported trusted call type")] + InvalidTrustedCallType, + #[error("SGX error, status: {0}")] + Sgx(sgx_status_t), + #[error("State handling error: {0}")] + StateHandler(#[from] itp_stf_state_handler::error::Error), + #[error("State observer error: {0}")] + StateObserver(#[from] itp_stf_state_observer::error::Error), + #[error("Node metadata error: {0:?}")] + NodeMetadata(itp_node_api::metadata::Error), + #[error("Node metadata provider error: {0:?}")] + NodeMetadataProvider(#[from] itp_node_api::metadata::provider::Error), + #[error("STF error: {0}")] + Stf(ita_stf::StfError), + #[error("Ocall Api error: {0}")] + OcallApi(itp_ocall_api::Error), + #[error("Crypto error: {0}")] + Crypto(itp_sgx_crypto::error::Error), + #[error(transparent)] + Other(#[from] Box), +} + +impl From for Error { + fn from(sgx_status: sgx_status_t) -> Self { + Self::Sgx(sgx_status) + } +} + +impl From for Error { + fn from(e: codec::Error) -> Self { + Self::Other(format!("{:?}", e).into()) + } +} + +impl From for Error { + fn from(error: ita_stf::StfError) -> Self { + Self::Stf(error) + } +} + +impl From for Error { + fn from(error: itp_ocall_api::Error) -> Self { + Self::OcallApi(error) + } +} + +impl From for Error { + fn from(error: itp_sgx_crypto::error::Error) -> Self { + Self::Crypto(error) + } +} + +impl From for Error { + fn from(e: itp_node_api::metadata::Error) -> Self { + Self::NodeMetadata(e) + } +} diff --git a/tee-worker/core-primitives/stf-executor/src/executor.rs b/tee-worker/core-primitives/stf-executor/src/executor.rs new file mode 100644 index 0000000000..b4b833a539 --- /dev/null +++ b/tee-worker/core-primitives/stf-executor/src/executor.rs @@ -0,0 +1,345 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +use crate::{ + error::{Error, Result}, + traits::{StatePostProcessing, StateUpdateProposer, StfExecuteGenericUpdate, StfUpdateState}, + BatchExecutionResult, ExecutedOperation, +}; +use codec::{Decode, Encode}; +use ita_stf::{ + hash::{Hash, TrustedOperationOrHash}, + stf_sgx::{shards_key_hash, storage_hashes_to_update_per_shard}, + ParentchainHeader, ShardIdentifier, StfError, TrustedCallSigned, TrustedOperation, +}; +use itp_node_api::metadata::{ + pallet_imp::IMPCallIndexes, pallet_teerex::TeerexCallIndexes, provider::AccessNodeMetadata, +}; +use itp_ocall_api::{EnclaveAttestationOCallApi, EnclaveOnChainOCallApi}; +use itp_sgx_externalities::{SgxExternalitiesTrait, StateHash}; +use itp_stf_interface::{ + parentchain_pallet::ParentchainPalletInterface, ExecuteCall, StateCallInterface, UpdateState, +}; +use itp_stf_state_handler::{handle_state::HandleState, query_shard_state::QueryShardState}; +use itp_time_utils::duration_now; +use itp_types::{storage::StorageEntryVerified, OpaqueCall, H256}; +use log::*; +use sp_runtime::traits::Header as HeaderTrait; +use std::{ + boxed::Box, collections::BTreeMap, fmt::Debug, format, marker::PhantomData, + result::Result as StdResult, sync::Arc, time::Duration, vec::Vec, +}; + +pub struct StfExecutor { + ocall_api: Arc, + state_handler: Arc, + node_metadata_repo: Arc, + _phantom: PhantomData, +} + +impl + StfExecutor +where + OCallApi: EnclaveAttestationOCallApi + EnclaveOnChainOCallApi, + StateHandler: HandleState, + StateHandler::StateT: SgxExternalitiesTrait + Encode, + NodeMetadataRepository: AccessNodeMetadata, + NodeMetadataRepository::MetadataType: TeerexCallIndexes + IMPCallIndexes, + Stf: UpdateState< + StateHandler::StateT, + ::SgxExternalitiesDiffType, + > + StateCallInterface, + ::SgxExternalitiesDiffType: + IntoIterator, Option>)> + From, Option>>>, + >::Error: Debug, +{ + pub fn new( + ocall_api: Arc, + state_handler: Arc, + node_metadata_repo: Arc, + ) -> Self { + StfExecutor { ocall_api, state_handler, node_metadata_repo, _phantom: PhantomData } + } + + /// Execute a trusted call on the STF + /// + /// We distinguish between an error in the execution, which maps to `Err` and + /// an invalid trusted call, which results in `Ok(ExecutionStatus::Failure)`. The latter + /// can be used to remove the trusted call from a queue. In the former case we might keep the + /// trusted call and just re-try the operation. + fn execute_trusted_call_on_stf( + &self, + state: &mut StateHandler::StateT, + trusted_operation: &TrustedOperation, + header: &PH, + shard: &ShardIdentifier, + post_processing: StatePostProcessing, + ) -> Result + where + PH: HeaderTrait, + { + debug!("query mrenclave of self"); + let mrenclave = self.ocall_api.get_mrenclave_of_self()?; + + let top_or_hash = TrustedOperationOrHash::from_top(trusted_operation.clone()); + + let trusted_call = match trusted_operation.to_call().ok_or(Error::InvalidTrustedCallType) { + Ok(c) => c, + Err(e) => { + error!("Error: {:?}", e); + return Ok(ExecutedOperation::failed(top_or_hash)) + }, + }; + + if let false = trusted_call.verify_signature(&mrenclave.m, &shard) { + error!("TrustedCallSigned: bad signature"); + return Ok(ExecutedOperation::failed(top_or_hash)) + } + + // Necessary because light client sync may not be up to date + // see issue #208 + debug!("Update STF storage!"); + + // TODO: otherwise I got compile error: + // cannot infer type for type parameter `NodeMetadataRepository` declared on the trait `ExecuteCall` + let trusted_call_executor: Box> = Box::new(trusted_call.clone()); + let storage_hashes = trusted_call_executor.get_storage_hashes_to_update(); + let update_map = self + .ocall_api + .get_multiple_storages_verified(storage_hashes, header) + .map(into_map)?; + + debug!("Apply state diff with {} entries from parentchain block", update_map.len()); + Stf::apply_state_diff(state, update_map.into()); + + debug!("execute on STF, call with nonce {}", trusted_call.nonce); + let mut extrinsic_call_backs: Vec = Vec::new(); + if let Err(e) = Stf::execute_call( + state, + shard, + trusted_call.clone(), + &mut extrinsic_call_backs, + self.node_metadata_repo.clone(), + ) { + error!("Stf execute failed: {:?}", e); + return Ok(ExecutedOperation::failed(top_or_hash)) + } + + let operation_hash = trusted_operation.hash(); + debug!("Operation hash {:?}", operation_hash); + + if let StatePostProcessing::Prune = post_processing { + state.prune_state_diff(); + } + + Ok(ExecutedOperation::success(operation_hash, top_or_hash, extrinsic_call_backs)) + } +} + +impl StfUpdateState + for StfExecutor +where + OCallApi: EnclaveAttestationOCallApi + EnclaveOnChainOCallApi, + StateHandler: HandleState + QueryShardState, + StateHandler::StateT: SgxExternalitiesTrait + Encode, + NodeMetadataRepository: AccessNodeMetadata, + Stf: UpdateState< + StateHandler::StateT, + ::SgxExternalitiesDiffType, + > + ParentchainPalletInterface, + ::SgxExternalitiesDiffType: + IntoIterator, Option>)>, + >::Error: Debug, + ::SgxExternalitiesDiffType: + From, Option>>>, +{ + fn update_states(&self, header: &ParentchainHeader) -> Result<()> { + debug!("Update STF storage upon block import!"); + let storage_hashes = Stf::storage_hashes_to_update_on_block(); + + if storage_hashes.is_empty() { + return Ok(()) + } + + // global requests they are the same for every shard + let state_diff_update = self + .ocall_api + .get_multiple_storages_verified(storage_hashes, header) + .map(into_map)?; + + // Update parentchain block on all states. + let shards = self.state_handler.list_shards()?; + for shard_id in shards { + let (state_lock, mut state) = self.state_handler.load_for_mutation(&shard_id)?; + match Stf::update_parentchain_block(&mut state, header.clone()) { + Ok(_) => { + self.state_handler.write_after_mutation(state, state_lock, &shard_id)?; + }, + Err(e) => error!("Could not update parentchain block. {:?}: {:?}", shard_id, e), + } + } + + // look for new shards an initialize them + if let Some(maybe_shards) = state_diff_update.get(&shards_key_hash()) { + match maybe_shards { + Some(shards) => { + let shards: Vec = Decode::decode(&mut shards.as_slice())?; + + for shard_id in shards { + let (state_lock, mut state) = + self.state_handler.load_for_mutation(&shard_id)?; + trace!("Successfully loaded state, updating states ..."); + + // per shard (cid) requests + let per_shard_hashes = storage_hashes_to_update_per_shard(&shard_id); + let per_shard_update = self + .ocall_api + .get_multiple_storages_verified(per_shard_hashes, header) + .map(into_map)?; + + Stf::apply_state_diff(&mut state, per_shard_update.into()); + Stf::apply_state_diff(&mut state, state_diff_update.clone().into()); + if let Err(e) = Stf::update_parentchain_block(&mut state, header.clone()) { + error!("Could not update parentchain block. {:?}: {:?}", shard_id, e) + } + + self.state_handler.write_after_mutation(state, state_lock, &shard_id)?; + } + }, + None => debug!("No shards are on the chain yet"), + }; + }; + Ok(()) + } +} + +impl StateUpdateProposer + for StfExecutor +where + OCallApi: EnclaveAttestationOCallApi + EnclaveOnChainOCallApi, + StateHandler: HandleState, + StateHandler::StateT: SgxExternalitiesTrait + Encode + StateHash, + ::SgxExternalitiesType: Encode, + NodeMetadataRepository: AccessNodeMetadata, + NodeMetadataRepository::MetadataType: TeerexCallIndexes + IMPCallIndexes, + Stf: UpdateState< + StateHandler::StateT, + ::SgxExternalitiesDiffType, + > + StateCallInterface, + ::SgxExternalitiesDiffType: + IntoIterator, Option>)>, + ::SgxExternalitiesDiffType: + From, Option>>>, + >::Error: Debug, +{ + type Externalities = StateHandler::StateT; + + fn propose_state_update( + &self, + trusted_calls: &[TrustedOperation], + header: &PH, + shard: &ShardIdentifier, + max_exec_duration: Duration, + prepare_state_function: F, + ) -> Result> + where + PH: HeaderTrait, + F: FnOnce(Self::Externalities) -> Self::Externalities, + { + let ends_at = duration_now() + max_exec_duration; + + let state = self.state_handler.load(shard)?; + let state_hash_before_execution = state.hash(); + + // Execute any pre-processing steps. + let mut state = prepare_state_function(state); + let mut executed_and_failed_calls = Vec::::new(); + + // Iterate through all calls until time is over. + for trusted_call_signed in trusted_calls.into_iter() { + // Break if allowed time window is over. + if ends_at < duration_now() { + break + } + + match self.execute_trusted_call_on_stf( + &mut state, + &trusted_call_signed, + header, + shard, + StatePostProcessing::None, + ) { + Ok(executed_or_failed_call) => { + executed_and_failed_calls.push(executed_or_failed_call); + }, + Err(e) => { + error!("Fatal Error. Failed to attempt call execution: {:?}", e); + }, + }; + } + + Ok(BatchExecutionResult { + executed_operations: executed_and_failed_calls, + state_hash_before_execution, + state_after_execution: state, + }) + } +} + +impl StfExecuteGenericUpdate + for StfExecutor +where + StateHandler: HandleState, + StateHandler::StateT: SgxExternalitiesTrait + Encode, + NodeMetadataRepository: AccessNodeMetadata, + Stf: UpdateState< + StateHandler::StateT, + ::SgxExternalitiesDiffType, + >, + ::SgxExternalitiesDiffType: + IntoIterator, Option>)>, +{ + type Externalities = StateHandler::StateT; + + fn execute_update( + &self, + shard: &ShardIdentifier, + update_function: F, + ) -> Result<(ResultT, H256)> + where + F: FnOnce(Self::Externalities) -> StdResult<(Self::Externalities, ResultT), ErrorT>, + ErrorT: Debug, + { + let (state_lock, state) = self.state_handler.load_for_mutation(&shard)?; + + let (new_state, result) = update_function(state).map_err(|e| { + Error::Other(format!("Failed to run update function on STF state: {:?}", e).into()) + })?; + + let new_state_hash = self + .state_handler + .write_after_mutation(new_state, state_lock, shard) + .map_err(|e| Error::StateHandler(e))?; + Ok((result, new_state_hash)) + } +} + +fn into_map( + storage_entries: Vec>>, +) -> BTreeMap, Option>> { + storage_entries.into_iter().map(|e| e.into_tuple()).collect() +} diff --git a/tee-worker/core-primitives/stf-executor/src/executor_tests.rs b/tee-worker/core-primitives/stf-executor/src/executor_tests.rs new file mode 100644 index 0000000000..c8f0299bc5 --- /dev/null +++ b/tee-worker/core-primitives/stf-executor/src/executor_tests.rs @@ -0,0 +1,261 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +use crate::{ + error::Error, + executor::*, + traits::{StateUpdateProposer, StfExecuteGenericUpdate}, +}; +use codec::Encode; +use ita_stf::{ + stf_sgx_tests::StfState, + test_genesis::{endowed_account, test_genesis_setup}, + ShardIdentifier, State, TrustedCall, +}; +use itc_parentchain_test::parentchain_header_builder::ParentchainHeaderBuilder; +use itp_node_api::metadata::{metadata_mocks::NodeMetadataMock, provider::NodeMetadataRepository}; +use itp_ocall_api::EnclaveAttestationOCallApi; +use itp_sgx_externalities::{SgxExternalitiesTrait, StateHash}; +use itp_stf_state_handler::handle_state::HandleState; +use itp_test::mock::{handle_state_mock::HandleStateMock, onchain_mock::OnchainMock}; +use itp_types::H256; +use sp_core::Pair; +use sp_runtime::app_crypto::sp_core::blake2_256; +use std::{sync::Arc, time::Duration, vec}; + +// FIXME: Create unit tests for update_states, execute_shield_funds, execute_trusted_call, execute_trusted_call_on_stf #554 + +pub fn propose_state_update_executes_all_calls_given_enough_time() { + // given + let (stf_executor, ocall_api, state_handler) = stf_executor(); + let mrenclave = ocall_api.get_mrenclave_of_self().unwrap().m; + let (_, shard) = init_state_and_shard_with_state_handler(state_handler.as_ref()); + let sender = endowed_account(); + let signed_call_1 = TrustedCall::balance_transfer( + sender.public().into(), + sender.public().into(), + 42, + ) + .sign(&sender.clone().into(), 0, &mrenclave, &shard); + let trusted_operation_1 = signed_call_1.into_trusted_operation(true); + let call_operation_hash_1: H256 = blake2_256(&trusted_operation_1.encode()).into(); + let signed_call_2 = TrustedCall::balance_transfer( + sender.public().into(), + sender.public().into(), + 100, + ) + .sign(&sender.clone().into(), 1, &mrenclave, &shard); + let trusted_operation_2 = signed_call_2.into_trusted_operation(true); + let call_operation_hash_2: H256 = blake2_256(&trusted_operation_2.encode()).into(); + + let old_state_hash = &state_handler.load(&shard).unwrap().hash(); + + // when + let batch_execution_result = stf_executor + .propose_state_update( + &vec![trusted_operation_1, trusted_operation_2], + &ParentchainHeaderBuilder::default().build(), + &shard, + Duration::from_secs(1000), + |state| state, + ) + .unwrap(); + + // then + assert_eq!(*old_state_hash, batch_execution_result.state_hash_before_execution); + assert_eq!(batch_execution_result.executed_operations.len(), 2); + assert_eq!( + batch_execution_result.get_executed_operation_hashes(), + vec![call_operation_hash_1, call_operation_hash_2] + ); + // Ensure that state has been updated and not actually written. + assert_ne!(state_handler.load(&shard).unwrap(), batch_execution_result.state_after_execution); +} + +pub fn propose_state_update_executes_only_one_trusted_call_given_not_enough_time() { + // given + let (stf_executor, ocall_api, state_handler) = stf_executor(); + let mrenclave = ocall_api.get_mrenclave_of_self().unwrap().m; + let (_, shard) = init_state_and_shard_with_state_handler(state_handler.as_ref()); + let sender = endowed_account(); + let signed_call_1 = TrustedCall::balance_transfer( + sender.public().into(), + sender.public().into(), + 42, + ) + .sign(&sender.clone().into(), 0, &mrenclave, &shard); + let trusted_operation_1 = signed_call_1.into_trusted_operation(true); + let call_operation_hash_1: H256 = blake2_256(&trusted_operation_1.encode()).into(); + + let signed_call_2 = TrustedCall::balance_transfer( + sender.public().into(), + sender.public().into(), + 100, + ) + .sign(&sender.clone().into(), 0, &mrenclave, &shard); + let trusted_operation_2 = signed_call_2.into_trusted_operation(true); + + let old_state_hash = &state_handler.load(&shard).unwrap().hash(); + + // when + let batch_execution_result = stf_executor + .propose_state_update( + &vec![trusted_operation_1.clone(), trusted_operation_2.clone()], + &ParentchainHeaderBuilder::default().build(), + &shard, + Duration::from_nanos(50_000), + |state| state, + ) + .unwrap(); + + // then + assert_eq!(*old_state_hash, batch_execution_result.state_hash_before_execution); + assert_eq!(batch_execution_result.executed_operations.len(), 1); + assert_eq!(batch_execution_result.get_executed_operation_hashes(), vec![call_operation_hash_1]); + // Ensure that state has been updated and not actually written. + assert_ne!(state_handler.load(&shard).unwrap(), batch_execution_result.state_after_execution); +} + +pub fn propose_state_update_executes_no_trusted_calls_given_no_time() { + // given + let (stf_executor, ocall_api, state_handler) = stf_executor(); + let mrenclave = ocall_api.get_mrenclave_of_self().unwrap().m; + let (_, shard) = init_state_and_shard_with_state_handler(state_handler.as_ref()); + let sender = endowed_account(); + let signed_call_1 = TrustedCall::balance_transfer( + sender.public().into(), + sender.public().into(), + 42, + ) + .sign(&sender.clone().into(), 0, &mrenclave, &shard); + let trusted_operation_1 = signed_call_1.into_trusted_operation(true); + + let signed_call_2 = TrustedCall::balance_transfer( + sender.public().into(), + sender.public().into(), + 100, + ) + .sign(&sender.clone().into(), 0, &mrenclave, &shard); + let trusted_operation_2 = signed_call_2.into_trusted_operation(true); + + let old_state_hash = &state_handler.load(&shard).unwrap().hash(); + + // when + let batch_execution_result = stf_executor + .propose_state_update( + &vec![trusted_operation_1.clone(), trusted_operation_2.clone()], + &ParentchainHeaderBuilder::default().build(), + &shard, + Duration::ZERO, + |state| state, + ) + .unwrap(); + + // then + assert_eq!(*old_state_hash, batch_execution_result.state_hash_before_execution); + assert_eq!(batch_execution_result.executed_operations.len(), 0); + assert_eq!(batch_execution_result.get_executed_operation_hashes(), vec![]); +} + +pub fn propose_state_update_always_executes_preprocessing_step() { + // given + let shard = ShardIdentifier::default(); + let (stf_executor, _, state_handler) = stf_executor(); + let _init_hash = state_handler.initialize_shard(shard).unwrap(); + let key = "my_key".encode(); + let value = "my_value".encode(); + let old_state_hash = state_handler.load(&shard).unwrap().hash(); + + // when + let batch_execution_result = stf_executor + .propose_state_update( + &vec![], + &ParentchainHeaderBuilder::default().build(), + &shard, + Duration::ZERO, + |mut state| { + state.insert(key.clone(), value.clone()); + state + }, + ) + .unwrap(); + + // then + assert_eq!(old_state_hash, batch_execution_result.state_hash_before_execution); + + // Ensure that state has been updated. + let old_state = state_handler.load(&shard).unwrap(); + let retrieved_value = batch_execution_result.state_after_execution.get(key.as_slice()).unwrap(); + assert_eq!(*retrieved_value, value); + // Ensure that state has not been actually written. + assert_ne!(old_state, batch_execution_result.state_after_execution); +} + +pub fn execute_update_works() { + // given + let shard = ShardIdentifier::default(); + let (stf_executor, _ocall_api, state_handler) = stf_executor(); + let _init_hash = state_handler.initialize_shard(shard).unwrap(); + let key = "my_key".encode(); + let value = "my_value".encode(); + let old_state_hash = state_handler.load(&shard).unwrap().hash(); + + // when + let (result, updated_state_hash) = stf_executor + .execute_update::<_, _, Error>(&shard, |mut state| { + state.insert(key.clone(), value.clone()); + Ok((state, 0)) + }) + .unwrap(); + + // then + assert_eq!(result, 0); + assert_ne!(updated_state_hash, old_state_hash); + + // Ensure that state has been written. + let updated_state = state_handler.load(&shard).unwrap(); + let retrieved_value = updated_state.get(key.as_slice()).unwrap(); + assert_eq!(*retrieved_value, value); +} + +// Helper Functions +fn stf_executor() -> ( + StfExecutor, StfState>, + Arc, + Arc, +) { + let ocall_api = Arc::new(OnchainMock::default()); + let state_handler = Arc::new(HandleStateMock::default()); + let node_metadata_repo = Arc::new(NodeMetadataRepository::new(NodeMetadataMock::new())); + let executor = StfExecutor::new(ocall_api.clone(), state_handler.clone(), node_metadata_repo); + (executor, ocall_api, state_handler) +} + +/// Returns a test setup initialized `State` with the corresponding `ShardIdentifier`. +pub(crate) fn init_state_and_shard_with_state_handler>( + state_handler: &S, +) -> (State, ShardIdentifier) { + let shard = ShardIdentifier::default(); + let _hash = state_handler.initialize_shard(shard).unwrap(); + + let (lock, mut state) = state_handler.load_for_mutation(&shard).unwrap(); + test_genesis_setup(&mut state); + + state_handler.write_after_mutation(state.clone(), lock, &shard).unwrap(); + + (state, shard) +} diff --git a/tee-worker/core-primitives/stf-executor/src/getter_executor.rs b/tee-worker/core-primitives/stf-executor/src/getter_executor.rs new file mode 100644 index 0000000000..d815748005 --- /dev/null +++ b/tee-worker/core-primitives/stf-executor/src/getter_executor.rs @@ -0,0 +1,139 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +//! Getter executor uses the state observer to get the most recent state and runs the getter on it. +//! The getter is verified (signature verfification) inside the `GetState` implementation. + +use crate::{ + error::{Error, Result}, + state_getter::GetState, +}; +use codec::Decode; +use ita_stf::Getter; +use itp_stf_state_observer::traits::ObserveState; +use itp_types::ShardIdentifier; +use log::*; +use std::{format, marker::PhantomData, sync::Arc, time::Instant, vec::Vec}; + +/// Trait to execute a getter for a specific shard. +pub trait ExecuteGetter { + fn execute_getter( + &self, + shard: &ShardIdentifier, + encoded_signed_getter: Vec, + ) -> Result>>; +} + +pub struct GetterExecutor { + state_observer: Arc, + _phantom: PhantomData, +} + +impl GetterExecutor { + pub fn new(state_observer: Arc) -> Self { + Self { state_observer, _phantom: Default::default() } + } +} + +impl ExecuteGetter for GetterExecutor +where + StateObserver: ObserveState, + StateGetter: GetState, +{ + fn execute_getter( + &self, + shard: &ShardIdentifier, + encoded_signed_getter: Vec, + ) -> Result>> { + let getter: Getter = Decode::decode(&mut encoded_signed_getter.as_slice())?; + + trace!("Successfully decoded trusted getter"); + if let Getter::trusted(trusted_getter_signed) = getter { + let getter_timer_start = Instant::now(); + let state_result = self.state_observer.observe_state(shard, |state| { + StateGetter::get_state(&trusted_getter_signed, state) + })??; + + debug!("Getter executed in {} ms", getter_timer_start.elapsed().as_millis()); + + Ok(state_result) + } else { + Err(Error::Other(format!("Unsupported getter type: {:?}", getter).into())) + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use codec::{Decode, Encode}; + use ita_stf::{AccountId, PublicGetter, TrustedGetter, TrustedGetterSigned}; + use itp_stf_state_observer::mock::ObserveStateMock; + use sp_core::ed25519::Signature; + use sp_runtime::MultiSignature; + + type TestState = u64; + type TestStateObserver = ObserveStateMock; + + struct TestStateGetter; + impl GetState for TestStateGetter { + fn get_state( + _getter: &TrustedGetterSigned, + state: &mut TestState, + ) -> Result>> { + Ok(Some(state.encode())) + } + } + + type TestGetterExecutor = GetterExecutor; + + #[test] + fn executing_getters_works() { + let test_state = 23489u64; + let state_observer = Arc::new(TestStateObserver::new(test_state)); + let getter_executor = TestGetterExecutor::new(state_observer); + let getter = Getter::trusted(dummy_trusted_getter()); + + let state_result = getter_executor + .execute_getter(&ShardIdentifier::default(), getter.encode()) + .unwrap() + .unwrap(); + let decoded_state: TestState = Decode::decode(&mut state_result.as_slice()).unwrap(); + assert_eq!(decoded_state, test_state); + } + + #[test] + fn executing_public_getter_gives_error() { + // no support for public getters yet. + let getter = Getter::public(PublicGetter::some_value); + + let test_state = 23489u64; + let state_observer = Arc::new(TestStateObserver::new(test_state)); + let getter_executor = TestGetterExecutor::new(state_observer); + + assert!(getter_executor + .execute_getter(&ShardIdentifier::default(), getter.encode()) + .is_err()); + } + + fn dummy_trusted_getter() -> TrustedGetterSigned { + TrustedGetterSigned::new( + TrustedGetter::nonce(AccountId::new([0u8; 32])), + MultiSignature::Ed25519(Signature::from_raw([0u8; 64])), + ) + } +} diff --git a/tee-worker/core-primitives/stf-executor/src/lib.rs b/tee-worker/core-primitives/stf-executor/src/lib.rs new file mode 100644 index 0000000000..ae28cd7375 --- /dev/null +++ b/tee-worker/core-primitives/stf-executor/src/lib.rs @@ -0,0 +1,219 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +#![cfg_attr(not(feature = "std"), no_std)] +#![cfg_attr(test, feature(assert_matches))] + +#[cfg(all(feature = "std", feature = "sgx"))] +compile_error!("feature \"std\" and feature \"sgx\" cannot be enabled at the same time"); + +#[cfg(all(not(feature = "std"), feature = "sgx"))] +extern crate sgx_tstd as std; + +use codec::Encode; +use ita_stf::hash::TrustedOperationOrHash; +use itp_sgx_externalities::SgxExternalitiesTrait; +use itp_types::{OpaqueCall, H256}; +use std::vec::Vec; + +// re-export module to properly feature gate sgx and regular std environment +#[cfg(all(not(feature = "std"), feature = "sgx"))] +pub mod sgx_reexport_prelude { + pub use thiserror_sgx as thiserror; +} + +pub mod error; +pub mod getter_executor; +pub mod state_getter; +pub mod traits; + +#[cfg(feature = "sgx")] +pub mod executor; + +#[cfg(feature = "sgx")] +pub mod enclave_signer; + +#[cfg(all(feature = "sgx", feature = "test"))] +pub mod executor_tests; + +#[cfg(feature = "mocks")] +pub mod mocks; + +/// Execution status of a trusted operation +/// +/// In case of success, it includes the operation hash, as well as +/// any extrinsic callbacks (e.g. unshield extrinsics) that need to be executed on-chain +#[derive(Clone, Debug, PartialEq)] +pub enum ExecutionStatus { + Success(H256, Vec), + Failure, +} + +impl ExecutionStatus { + pub fn get_extrinsic_callbacks(&self) -> Vec { + match self { + ExecutionStatus::Success(_, opaque_calls) => opaque_calls.clone(), + _ => Vec::new(), + } + } + + pub fn get_executed_operation_hash(&self) -> Option { + match self { + ExecutionStatus::Success(operation_hash, _) => Some(*operation_hash), + _ => None, + } + } +} + +/// Information about an executed trusted operation +/// +/// +#[derive(Clone, Debug, PartialEq)] +pub struct ExecutedOperation { + pub status: ExecutionStatus, + pub trusted_operation_or_hash: TrustedOperationOrHash, +} + +impl ExecutedOperation { + /// Constructor for a successfully executed trusted operation. + pub fn success( + operation_hash: H256, + trusted_operation_or_hash: TrustedOperationOrHash, + extrinsic_call_backs: Vec, + ) -> Self { + ExecutedOperation { + status: ExecutionStatus::Success(operation_hash, extrinsic_call_backs), + trusted_operation_or_hash, + } + } + + /// Constructor for a failed trusted operation execution. + pub fn failed(trusted_operation_or_hash: TrustedOperationOrHash) -> Self { + ExecutedOperation { status: ExecutionStatus::Failure, trusted_operation_or_hash } + } + + /// Returns true if the executed operation was a success. + pub fn is_success(&self) -> bool { + matches!(self.status, ExecutionStatus::Success(_, _)) + } +} + +/// Result of an execution on the STF +/// +/// Contains multiple executed operations +#[derive(Clone, Debug)] +pub struct BatchExecutionResult { + pub state_hash_before_execution: H256, + pub executed_operations: Vec, + pub state_after_execution: Externalities, +} + +impl BatchExecutionResult +where + Externalities: SgxExternalitiesTrait + Encode, +{ + pub fn get_extrinsic_callbacks(&self) -> Vec { + self.executed_operations + .iter() + .flat_map(|e| e.status.get_extrinsic_callbacks()) + .collect() + } + + /// Returns all successfully exectued operation hashes. + pub fn get_executed_operation_hashes(&self) -> Vec { + self.executed_operations + .iter() + .flat_map(|ec| ec.status.get_executed_operation_hash()) + .collect() + } + + /// Returns all operations that were not executed. + pub fn get_failed_operations(&self) -> Vec { + self.executed_operations + .iter() + .flat_map(|ec| match ec.is_success() { + false => Some(ec.clone()), + true => None, + }) + .collect() + } +} + +#[cfg(test)] +mod tests { + use super::*; + use itp_sgx_externalities::SgxExternalities; + + #[test] + fn is_success_works() { + let (success, _) = create_success_operation_from_u8(1); + let failed = create_failed_operation_from_u8(7); + + assert!(success.is_success()); + assert!(!failed.is_success()); + } + + #[test] + fn get_executed_operation_hashes_works() { + let (success_one, hash_success_one) = create_success_operation_from_u8(1); + let (success_two, hash_success_two) = create_success_operation_from_u8(3); + let failed = create_failed_operation_from_u8(7); + let result = batch_execution_result(vec![success_one, failed, success_two]); + + let success_operations = result.get_executed_operation_hashes(); + + assert_eq!(success_operations.len(), 2); + assert!(success_operations.contains(&hash_success_one)); + assert!(success_operations.contains(&hash_success_two)); + } + + #[test] + fn get_failed_operations_works() { + let failed_one = create_failed_operation_from_u8(1); + let failed_two = create_failed_operation_from_u8(3); + let (success, _) = create_success_operation_from_u8(10); + let result = batch_execution_result(vec![failed_one.clone(), failed_two.clone(), success]); + + let failed_operations = result.get_failed_operations(); + + assert_eq!(failed_operations.len(), 2); + assert!(failed_operations.contains(&failed_one)); + assert!(failed_operations.contains(&failed_two)); + } + + fn batch_execution_result( + executed_calls: Vec, + ) -> BatchExecutionResult { + BatchExecutionResult { + executed_operations: executed_calls, + state_hash_before_execution: H256::default(), + state_after_execution: SgxExternalities::default(), + } + } + + fn create_failed_operation_from_u8(int: u8) -> ExecutedOperation { + ExecutedOperation::failed(TrustedOperationOrHash::Hash(H256::from([int; 32]))) + } + + fn create_success_operation_from_u8(int: u8) -> (ExecutedOperation, H256) { + let hash = H256::from([int; 32]); + let opaque_call: Vec = vec![OpaqueCall(vec![int; 10])]; + let operation = + ExecutedOperation::success(hash, TrustedOperationOrHash::Hash(hash), opaque_call); + (operation, hash) + } +} diff --git a/tee-worker/core-primitives/stf-executor/src/mocks.rs b/tee-worker/core-primitives/stf-executor/src/mocks.rs new file mode 100644 index 0000000000..b3abf15494 --- /dev/null +++ b/tee-worker/core-primitives/stf-executor/src/mocks.rs @@ -0,0 +1,147 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +use crate::{ + error::Result, + state_getter::GetState, + traits::{StateUpdateProposer, StfEnclaveSigning}, + BatchExecutionResult, ExecutedOperation, +}; +use codec::Encode; +use ita_stf::{ + hash::{Hash, TrustedOperationOrHash}, + AccountId, KeyPair, ShardIdentifier, TrustedCall, TrustedCallSigned, TrustedGetterSigned, + TrustedOperation, +}; +use itp_sgx_externalities::SgxExternalitiesTrait; +use itp_types::H256; +use sp_core::Pair; +use sp_runtime::traits::Header as HeaderTrait; +use std::{boxed::Box, marker::PhantomData, ops::Deref, time::Duration, vec::Vec}; + +#[cfg(feature = "std")] +use std::sync::RwLock; + +#[cfg(feature = "sgx")] +use std::sync::SgxRwLock as RwLock; + +/// Mock for the StfExecutor. +#[derive(Default)] +pub struct StfExecutorMock { + pub state: RwLock, +} + +impl StfExecutorMock { + pub fn new(state: State) -> Self { + Self { state: RwLock::new(state) } + } + + pub fn get_state(&self) -> State { + (*self.state.read().unwrap().deref()).clone() + } +} + +impl StateUpdateProposer for StfExecutorMock +where + State: SgxExternalitiesTrait + Encode + Clone, +{ + type Externalities = State; + + fn propose_state_update( + &self, + trusted_calls: &[TrustedOperation], + _header: &PH, + _shard: &ShardIdentifier, + _max_exec_duration: Duration, + prepare_state_function: F, + ) -> Result> + where + PH: HeaderTrait, + F: FnOnce(Self::Externalities) -> Self::Externalities, + { + let mut lock = self.state.write().unwrap(); + + let updated_state = prepare_state_function((*lock.deref()).clone()); + + *lock = updated_state.clone(); + + let executed_operations: Vec = trusted_calls + .iter() + .map(|c| { + let operation_hash = c.hash(); + let top_or_hash = TrustedOperationOrHash::from_top(c.clone()); + ExecutedOperation::success(operation_hash, top_or_hash, Vec::new()) + }) + .collect(); + + Ok(BatchExecutionResult { + executed_operations, + state_hash_before_execution: H256::default(), + state_after_execution: updated_state, + }) + } +} + +/// Enclave signer mock. +pub struct StfEnclaveSignerMock { + mr_enclave: [u8; 32], + signer: sp_core::ed25519::Pair, +} + +impl StfEnclaveSignerMock { + pub fn new(mr_enclave: [u8; 32]) -> Self { + type Seed = [u8; 32]; + const TEST_SEED: Seed = *b"42345678901234567890123456789012"; + + Self { mr_enclave, signer: sp_core::ed25519::Pair::from_seed(&TEST_SEED) } + } +} + +impl Default for StfEnclaveSignerMock { + fn default() -> Self { + Self::new([0u8; 32]) + } +} + +impl StfEnclaveSigning for StfEnclaveSignerMock { + fn get_enclave_account(&self) -> Result { + Ok(self.signer.public().into()) + } + + fn sign_call_with_self( + &self, + trusted_call: &TrustedCall, + shard: &ShardIdentifier, + ) -> Result { + Ok(trusted_call.sign(&KeyPair::Ed25519(Box::new(self.signer)), 1, &self.mr_enclave, shard)) + } +} + +/// GetState mock +#[derive(Default)] +pub struct GetStateMock { + _phantom: PhantomData, +} + +impl GetState for GetStateMock +where + StateType: Encode, +{ + fn get_state(_getter: &TrustedGetterSigned, state: &mut StateType) -> Result>> { + Ok(Some(state.encode())) + } +} diff --git a/tee-worker/core-primitives/stf-executor/src/state_getter.rs b/tee-worker/core-primitives/stf-executor/src/state_getter.rs new file mode 100644 index 0000000000..5cedb93101 --- /dev/null +++ b/tee-worker/core-primitives/stf-executor/src/state_getter.rs @@ -0,0 +1,92 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +use crate::error::{Error, Result}; +use core::marker::PhantomData; +use ita_stf::{Getter, TrustedGetterSigned}; +use itp_sgx_externalities::SgxExternalities; +use itp_stf_interface::StateGetterInterface; +use log::debug; +use std::vec::Vec; + +/// Abstraction for accessing state with a getter. +pub trait GetState { + /// Executes a trusted getter on a state and return its value, if available. + /// + /// Also verifies the signature of the trusted getter and returns an error + /// if it's invalid. + fn get_state(getter: &TrustedGetterSigned, state: &mut StateType) -> Result>>; +} + +pub struct StfStateGetter { + _phantom: PhantomData, +} + +impl GetState for StfStateGetter +where + Stf: StateGetterInterface, +{ + fn get_state( + getter: &TrustedGetterSigned, + state: &mut SgxExternalities, + ) -> Result>> { + debug!("verifying signature of TrustedGetterSigned"); + // FIXME: Trusted Getter should not be hardcoded. But + // verify_signature is currently not available as a Trait. + if let false = getter.verify_signature() { + return Err(Error::OperationHasInvalidSignature) + } + debug!("calling into STF to get state"); + Ok(Stf::execute_getter(state, getter.clone().into())) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use core::assert_matches::assert_matches; + use ita_stf::TrustedGetter; + use itp_sgx_externalities::SgxExternalitiesDiffType; + use itp_stf_interface::mocks::StateInterfaceMock; + use itp_types::AccountId; + use sp_core::{ed25519, Pair}; + + type TestStf = StateInterfaceMock; + type TestStateGetter = StfStateGetter; + + #[test] + fn upon_false_signature_get_stf_state_errs() { + let sender = AccountId::from([0; 32]); + let wrong_signer = ed25519::Pair::from_seed(b"12345678901234567890123456789012"); + let signed_getter = TrustedGetter::free_balance(sender).sign(&wrong_signer.into()); + let mut state = SgxExternalities::default(); + + assert_matches!( + TestStateGetter::get_state(&signed_getter, &mut state), + Err(Error::OperationHasInvalidSignature) + ); + } + + #[test] + fn state_getter_is_executed_if_signature_is_correct() { + let sender = ed25519::Pair::from_seed(b"12345678901234567890123456789012"); + let signed_getter = + TrustedGetter::free_balance(sender.public().into()).sign(&sender.into()); + let mut state = SgxExternalities::default(); + assert!(TestStateGetter::get_state(&signed_getter, &mut state).is_ok()); + } +} diff --git a/tee-worker/core-primitives/stf-executor/src/traits.rs b/tee-worker/core-primitives/stf-executor/src/traits.rs new file mode 100644 index 0000000000..6a5f62f218 --- /dev/null +++ b/tee-worker/core-primitives/stf-executor/src/traits.rs @@ -0,0 +1,87 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +use crate::{error::Result, BatchExecutionResult}; +use codec::Encode; +use ita_stf::{ + AccountId, ParentchainHeader, ShardIdentifier, TrustedCall, TrustedCallSigned, TrustedOperation, +}; +use itp_sgx_externalities::SgxExternalitiesTrait; +use itp_types::H256; +use sp_runtime::traits::Header as HeaderTrait; +use std::{fmt::Debug, result::Result as StdResult, time::Duration}; + +/// Post-processing steps after executing STF +pub enum StatePostProcessing { + None, + Prune, +} + +/// Allows signing of a trusted call with the enclave account that is registered in the STF. +/// +/// The signing key is derived from the shielding key, which guarantees that all enclaves sign the same key. +pub trait StfEnclaveSigning { + fn get_enclave_account(&self) -> Result; + + fn sign_call_with_self( + &self, + trusted_call: &TrustedCall, + shard: &ShardIdentifier, + ) -> Result; +} + +/// Proposes a state update to `Externalities`. +pub trait StateUpdateProposer { + type Externalities: SgxExternalitiesTrait + Encode; + + /// Executes trusted calls within a given time frame without permanent state mutation. + /// + /// All executed call hashes and the mutated state are returned. + /// If the time expires, any remaining trusted calls within the batch will be ignored. + fn propose_state_update( + &self, + trusted_calls: &[TrustedOperation], + header: &PH, + shard: &ShardIdentifier, + max_exec_duration: Duration, + prepare_state_function: F, + ) -> Result> + where + PH: HeaderTrait, + F: FnOnce(Self::Externalities) -> Self::Externalities; +} + +/// Execute a generic function on the STF state +pub trait StfExecuteGenericUpdate { + type Externalities: SgxExternalitiesTrait + Encode; + + fn execute_update( + &self, + shard: &ShardIdentifier, + update_function: F, + ) -> Result<(ResultT, H256)> + where + F: FnOnce(Self::Externalities) -> StdResult<(Self::Externalities, ResultT), ErrorT>, + ErrorT: Debug; +} + +/// Updates the STF state for a specific header. +/// +/// Cannot be implemented for a generic header currently, because the runtime expects a ParentchainHeader. +pub trait StfUpdateState { + fn update_states(&self, header: &ParentchainHeader) -> Result<()>; +} diff --git a/tee-worker/core-primitives/stf-interface/Cargo.toml b/tee-worker/core-primitives/stf-interface/Cargo.toml new file mode 100644 index 0000000000..4981be9d07 --- /dev/null +++ b/tee-worker/core-primitives/stf-interface/Cargo.toml @@ -0,0 +1,17 @@ +[package] +authors = ["Integritee AG "] +edition = "2021" +name = "itp-stf-interface" +version = "0.8.0" + +[dependencies] +itp-node-api-metadata = { path = "../node-api/metadata", default-features = false } +itp-node-api-metadata-provider = { path = "../node-api/metadata-provider", default-features = false } +itp-types = { default-features = false, path = "../types" } + +[features] +default = ["std"] +mocks = [] +std = [ + "itp-types/std", +] diff --git a/tee-worker/core-primitives/stf-interface/src/lib.rs b/tee-worker/core-primitives/stf-interface/src/lib.rs new file mode 100644 index 0000000000..46ce0cac19 --- /dev/null +++ b/tee-worker/core-primitives/stf-interface/src/lib.rs @@ -0,0 +1,100 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +//! Provides a state interface. +//! This allow to easily mock the stf and exchange it with another storage. + +#![cfg_attr(not(feature = "std"), no_std)] + +extern crate alloc; + +use alloc::{sync::Arc, vec::Vec}; +use itp_node_api_metadata::{pallet_imp::IMPCallIndexes, pallet_teerex::TeerexCallIndexes}; +use itp_node_api_metadata_provider::AccessNodeMetadata; +use itp_types::{OpaqueCall, ShardIdentifier}; + +#[cfg(feature = "mocks")] +pub mod mocks; +pub mod parentchain_pallet; +pub mod sudo_pallet; +pub mod system_pallet; + +/// Interface to initialize a new state. +pub trait InitState { + /// Initialize a new state for a given enclave account. + fn init_state(enclave_account: AccountId) -> State; +} + +/// Interface for all functions calls necessary to update an already +/// initialized state. +pub trait UpdateState { + /// Updates a given state for + fn apply_state_diff(state: &mut State, state_diff: StateDiff); + fn storage_hashes_to_update_on_block() -> Vec>; +} + +/// Interface to execute state mutating calls on a state. +pub trait StateCallInterface +where + NodeMetadataRepository: AccessNodeMetadata, + NodeMetadataRepository::MetadataType: TeerexCallIndexes + IMPCallIndexes, +{ + type Error; + + /// Execute a call on a specific state. Callbacks are added as an `OpaqueCall`. + fn execute_call( + state: &mut State, + shard: &ShardIdentifier, + call: Call, + calls: &mut Vec, + node_metadata_repo: Arc, + ) -> Result<(), Self::Error>; +} + +/// Interface to execute state reading getters on a state. +pub trait StateGetterInterface { + /// Execute a getter on a specific state. + fn execute_getter(state: &mut State, getter: Getter) -> Option>; +} + +/// Trait used to abstract the call execution. +pub trait ExecuteCall +where + NodeMetadataRepository: AccessNodeMetadata, + NodeMetadataRepository::MetadataType: TeerexCallIndexes + IMPCallIndexes, +{ + type Error; + + /// Execute a call. Callbacks are added as an `OpaqueCall`. + fn execute( + self, + shard: &ShardIdentifier, + calls: &mut Vec, + node_metadata_repo: Arc, + ) -> Result<(), Self::Error>; + + /// Get storages hashes that should be updated for a specific call. + fn get_storage_hashes_to_update(&self) -> Vec>; +} + +/// Trait used to abstract the getter execution. +pub trait ExecuteGetter { + /// Execute a getter. + fn execute(self) -> Option>; + /// Get storages hashes that should be updated for a specific getter. + fn get_storage_hashes_to_update(&self) -> Vec>; +} diff --git a/tee-worker/core-primitives/stf-interface/src/mocks.rs b/tee-worker/core-primitives/stf-interface/src/mocks.rs new file mode 100644 index 0000000000..5b7250e341 --- /dev/null +++ b/tee-worker/core-primitives/stf-interface/src/mocks.rs @@ -0,0 +1,122 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +//! Provides a mock which implements all traits within this crate. + +extern crate alloc; +use crate::{ + system_pallet::SystemPalletAccountInterface, ExecuteCall, ExecuteGetter, InitState, + StateCallInterface, StateGetterInterface, UpdateState, +}; +use alloc::{string::String, sync::Arc, vec::Vec}; +use core::marker::PhantomData; +use itp_node_api_metadata::metadata_mocks::NodeMetadataMock; +use itp_node_api_metadata_provider::NodeMetadataRepository; +use itp_types::{AccountId, Index, OpaqueCall, ShardIdentifier}; + +#[derive(Default)] +pub struct StateInterfaceMock { + _phantom: PhantomData<(State, StateDiff)>, +} + +impl InitState + for StateInterfaceMock +{ + fn init_state(_enclave_account: AccountId) -> State { + unimplemented!() + } +} + +impl UpdateState for StateInterfaceMock { + fn apply_state_diff(_state: &mut State, _state_diff: StateDiff) { + unimplemented!() + } + + fn storage_hashes_to_update_on_block() -> Vec> { + unimplemented!() + } +} + +impl + StateCallInterface> + for StateInterfaceMock +{ + type Error = String; + + fn execute_call( + _state: &mut State, + _shard: &ShardIdentifier, + _call: Call, + _calls: &mut Vec, + _node_metadata_repo: Arc>, + ) -> Result<(), Self::Error> { + unimplemented!() + } +} + +impl StateGetterInterface + for StateInterfaceMock +{ + fn execute_getter(_state: &mut State, _getter: Getter) -> Option> { + None + } +} + +impl SystemPalletAccountInterface + for StateInterfaceMock +{ + type AccountData = String; + type Index = Index; + + fn get_account_nonce(_state: &mut State, _account_id: &AccountId) -> Self::Index { + unimplemented!() + } + fn get_account_data(_state: &mut State, _account_id: &AccountId) -> Self::AccountData { + unimplemented!() + } +} + +pub struct CallExecutorMock; + +impl ExecuteCall> for CallExecutorMock { + type Error = String; + + fn execute( + self, + _shard: &ShardIdentifier, + _calls: &mut Vec, + _node_metadata_repo: Arc>, + ) -> Result<(), Self::Error> { + unimplemented!() + } + + fn get_storage_hashes_to_update(&self) -> Vec> { + unimplemented!() + } +} + +pub struct GetterExecutorMock; + +impl ExecuteGetter for GetterExecutorMock { + fn execute(self) -> Option> { + unimplemented!() + } + + fn get_storage_hashes_to_update(&self) -> Vec> { + unimplemented!() + } +} diff --git a/tee-worker/core-primitives/stf-interface/src/parentchain_pallet.rs b/tee-worker/core-primitives/stf-interface/src/parentchain_pallet.rs new file mode 100644 index 0000000000..c89138c25e --- /dev/null +++ b/tee-worker/core-primitives/stf-interface/src/parentchain_pallet.rs @@ -0,0 +1,27 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +/// Interface trait of the parentchain pallet. +pub trait ParentchainPalletInterface { + type Error; + + /// Updates the block number, block hash and parent hash of the parentchain block. + fn update_parentchain_block( + state: &mut State, + header: ParentchainHeader, + ) -> Result<(), Self::Error>; +} diff --git a/tee-worker/core-primitives/stf-interface/src/sudo_pallet.rs b/tee-worker/core-primitives/stf-interface/src/sudo_pallet.rs new file mode 100644 index 0000000000..afd2ed1dec --- /dev/null +++ b/tee-worker/core-primitives/stf-interface/src/sudo_pallet.rs @@ -0,0 +1,27 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +/// Interface trait of the sudo pallet. +pub trait SudoPalletInterface { + type AccountId; + + /// Get the root account for a given state. + fn get_root(state: &mut State) -> Self::AccountId; + + /// Get the enclave account for a given state. + fn get_enclave_account(state: &mut State) -> Self::AccountId; +} diff --git a/tee-worker/core-primitives/stf-interface/src/system_pallet.rs b/tee-worker/core-primitives/stf-interface/src/system_pallet.rs new file mode 100644 index 0000000000..82166e846e --- /dev/null +++ b/tee-worker/core-primitives/stf-interface/src/system_pallet.rs @@ -0,0 +1,53 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ +extern crate alloc; +use alloc::{boxed::Box, vec::Vec}; + +/// Interface trait of the system pallet for account specific data. +pub trait SystemPalletAccountInterface { + type Index; + type AccountData; + + /// Get the nonce for a given account and state. + fn get_account_nonce(state: &mut State, account_id: &AccountId) -> Self::Index; + + /// Get the account date for a given account and state. + fn get_account_data(state: &mut State, account: &AccountId) -> Self::AccountData; +} + +/// Interface trait of the system pallet for event specific interactions. +pub trait SystemPalletEventInterface { + type EventRecord; + type EventIndex; + type BlockNumber; + type Hash; + + /// Get a Vec of bounded events. + fn get_events(state: &mut State) -> Vec>; + + /// Get the count of the currently stored events. + fn get_event_count(state: &mut State) -> Self::EventIndex; + + /// Get the event topics + fn get_event_topics( + state: &mut State, + topic: &Self::Hash, + ) -> Vec<(Self::BlockNumber, Self::EventIndex)>; + + /// Reset everything event related. + fn reset_events(state: &mut State); +} diff --git a/tee-worker/core-primitives/stf-state-handler/Cargo.toml b/tee-worker/core-primitives/stf-state-handler/Cargo.toml new file mode 100644 index 0000000000..3ba78f033f --- /dev/null +++ b/tee-worker/core-primitives/stf-state-handler/Cargo.toml @@ -0,0 +1,74 @@ +[package] +authors = ["Integritee AG "] +edition = "2021" +name = "itp-stf-state-handler" +version = "0.9.0" + +[dependencies] +# sgx dependencies +sgx_tcrypto = { branch = "master", git = "https://github.com/apache/teaclave-sgx-sdk.git", optional = true } +sgx_tstd = { branch = "master", git = "https://github.com/apache/teaclave-sgx-sdk.git", optional = true } +sgx_types = { branch = "master", git = "https://github.com/apache/teaclave-sgx-sdk.git" } + +# local dependencies +ita-stf = { path = "../../app-libs/stf", default-features = false } +itp-hashing = { path = "../../core-primitives/hashing", default-features = false } +itp-settings = { path = "../../core-primitives/settings" } +itp-sgx-crypto = { path = "../../core-primitives/sgx/crypto", default-features = false } +itp-sgx-externalities = { default-features = false, path = "../../core-primitives/substrate-sgx/externalities" } +itp-sgx-io = { path = "../../core-primitives/sgx/io", default-features = false } +itp-stf-interface = { default-features = false, path = "../../core-primitives/stf-interface" } +itp-stf-state-observer = { path = "../stf-state-observer", default-features = false } +itp-time-utils = { path = "../../core-primitives/time-utils", default-features = false } +itp-types = { path = "../types", default-features = false } + +# sgx enabled external libraries +rust-base58_sgx = { package = "rust-base58", rev = "sgx_1.1.3", git = "https://github.com/mesalock-linux/rust-base58-sgx", optional = true, default-features = false, features = ["mesalock_sgx"] } +thiserror_sgx = { package = "thiserror", git = "https://github.com/mesalock-linux/thiserror-sgx", tag = "sgx_1.1.3", optional = true } + +# std compatible external libraries (make sure these versions match with the sgx-enabled ones above) +rust-base58 = { package = "rust-base58", version = "0.0.4", optional = true } +thiserror = { version = "1.0", optional = true } + +# no-std dependencies +codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } +lazy_static = { version = "1.1.0", features = ["spin_no_std"] } +log = { version = "0.4", default-features = false } +sp-core = { default-features = false, features = ["full_crypto"], git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.29" } + +[dev-dependencies] +itp-hashing = { path = "../../core-primitives/hashing", features = ["std"] } +itp-sgx-crypto = { path = "../../core-primitives/sgx/crypto", features = ["mocks"] } +itp-stf-state-observer = { path = "../stf-state-observer", features = ["mocks"] } + +[features] +default = ["std"] +sgx = [ + "sgx_tstd", + "sgx_tcrypto", + "rust-base58_sgx", + "ita-stf/sgx", + "itp-sgx-crypto/sgx", + "itp-sgx-externalities/sgx", + "itp-sgx-io/sgx", + "itp-stf-state-observer/sgx", + "itp-time-utils/sgx", + "thiserror_sgx", +] +std = [ + "rust-base58", + "ita-stf/std", + "itp-sgx-crypto/std", + "itp-sgx-externalities/std", + "itp-sgx-io/std", + "itp-stf-interface/std", + "itp-stf-state-observer/std", + "itp-time-utils/std", + "itp-types/std", + "thiserror", + "log/std", +] +test = [ + "itp-sgx-crypto/mocks", + "itp-stf-interface/mocks", +] diff --git a/tee-worker/core-primitives/stf-state-handler/src/error.rs b/tee-worker/core-primitives/stf-state-handler/src/error.rs new file mode 100644 index 0000000000..e2db62301c --- /dev/null +++ b/tee-worker/core-primitives/stf-state-handler/src/error.rs @@ -0,0 +1,88 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +#[cfg(all(not(feature = "std"), feature = "sgx"))] +use crate::sgx_reexport_prelude::*; + +#[cfg(feature = "std")] +use rust_base58::base58::FromBase58Error; + +#[cfg(feature = "sgx")] +use base58::FromBase58Error; + +use crate::state_snapshot_primitives::StateId; +use itp_types::ShardIdentifier; +use sgx_types::sgx_status_t; +use std::{boxed::Box, format, string::String}; + +pub type Result = core::result::Result; + +#[derive(Debug, thiserror::Error)] +pub enum Error { + #[error("Empty state repository")] + EmptyRepository, + #[error("State ID is invalid and does not exist: {0}")] + InvalidStateId(StateId), + #[error("Shard is invalid and does not exist: {0}")] + InvalidShard(ShardIdentifier), + #[error("State with hash {0} could not be found in the state repository")] + StateNotFoundInRepository(String), + #[error("State observer error: {0}")] + StateObserver(#[from] itp_stf_state_observer::error::Error), + #[error("Cache size for registry is zero")] + ZeroCacheSize, + #[error("Could not acquire lock, lock is poisoned")] + LockPoisoning, + #[error("OsString conversion error")] + OsStringConversion, + #[error("SGX crypto error: {0}")] + CryptoError(itp_sgx_crypto::Error), + #[error("SGX error, status: {0}")] + SgxError(sgx_status_t), + #[error(transparent)] + Other(#[from] Box), +} + +impl From for Error { + fn from(e: std::io::Error) -> Self { + Self::Other(e.into()) + } +} + +impl From for Error { + fn from(e: codec::Error) -> Self { + Self::Other(format!("{:?}", e).into()) + } +} + +impl From for Error { + fn from(sgx_status: sgx_status_t) -> Self { + Self::SgxError(sgx_status) + } +} + +impl From for Error { + fn from(crypto_error: itp_sgx_crypto::Error) -> Self { + Self::CryptoError(crypto_error) + } +} + +impl From for Error { + fn from(e: FromBase58Error) -> Self { + Self::Other(format!("{:?}", e).into()) + } +} diff --git a/tee-worker/core-primitives/stf-state-handler/src/file_io.rs b/tee-worker/core-primitives/stf-state-handler/src/file_io.rs new file mode 100644 index 0000000000..e8d522a9f5 --- /dev/null +++ b/tee-worker/core-primitives/stf-state-handler/src/file_io.rs @@ -0,0 +1,390 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +#[cfg(all(not(feature = "std"), feature = "sgx"))] +use crate::sgx_reexport_prelude::*; + +#[cfg(any(test, feature = "std"))] +use rust_base58::base58::ToBase58; + +#[cfg(feature = "sgx")] +use base58::ToBase58; + +#[cfg(any(test, feature = "sgx"))] +use itp_settings::files::ENCRYPTED_STATE_FILE; + +#[cfg(any(test, feature = "sgx"))] +use std::string::String; + +use crate::{error::Result, state_snapshot_primitives::StateId}; +use codec::Encode; +use itp_settings::files::SHARDS_PATH; +use itp_types::ShardIdentifier; +use log::error; +use std::{format, path::PathBuf, vec::Vec}; + +/// Trait to abstract file I/O for state. +pub trait StateFileIo { + type StateType; + type HashType; + + /// Load a state (returns error if it does not exist). + fn load( + &self, + shard_identifier: &ShardIdentifier, + state_id: StateId, + ) -> Result; + + /// Compute the state hash of a specific state (returns error if it does not exist). + /// + /// Requires loading and decoding of the state. Use only when loading the state repository on + /// initialization of the worker. Computing the state hash in other cases is the + /// StateHandler's responsibility. + fn compute_hash( + &self, + shard_identifier: &ShardIdentifier, + state_id: StateId, + ) -> Result; + + /// Initialize a new shard with a given state. + fn initialize_shard( + &self, + shard_identifier: &ShardIdentifier, + state_id: StateId, + state: &Self::StateType, + ) -> Result; + + /// Write the state. + fn write( + &self, + shard_identifier: &ShardIdentifier, + state_id: StateId, + state: &Self::StateType, + ) -> Result; + + /// Remove a state. + fn remove(&self, shard_identifier: &ShardIdentifier, state_id: StateId) -> Result<()>; + + /// Checks if a given shard directory exists and contains at least one state instance. + fn shard_exists(&self, shard_identifier: &ShardIdentifier) -> bool; + + /// Lists all shards. + fn list_shards(&self) -> Result>; + + /// List all states for a shard. + fn list_state_ids_for_shard(&self, shard_identifier: &ShardIdentifier) -> Result>; +} + +#[cfg(feature = "sgx")] +pub mod sgx { + + use super::*; + use crate::error::Error; + use base58::FromBase58; + use codec::Decode; + use core::fmt::Debug; + use itp_hashing::Hash; + use itp_sgx_crypto::{key_repository::AccessKey, StateCrypto}; + use itp_sgx_externalities::SgxExternalitiesTrait; + use itp_sgx_io::{read as io_read, write as io_write}; + use itp_types::H256; + use log::*; + use std::{fs, marker::PhantomData, path::Path, sync::Arc}; + + /// SGX state file I/O. + pub struct SgxStateFileIo { + state_key_repository: Arc, + _phantom: PhantomData, + } + + impl SgxStateFileIo + where + StateKeyRepository: AccessKey, + ::KeyType: StateCrypto, + State: SgxExternalitiesTrait, + { + pub fn new(state_key_repository: Arc) -> Self { + SgxStateFileIo { state_key_repository, _phantom: PhantomData } + } + + fn read(&self, path: &Path) -> Result> { + let mut bytes = io_read(path)?; + + if bytes.is_empty() { + return Ok(bytes) + } + + let state_key = self.state_key_repository.retrieve_key()?; + + state_key + .decrypt(&mut bytes) + .map_err(|e| Error::Other(format!("{:?}", e).into()))?; + trace!("buffer decrypted = {:?}", bytes); + + Ok(bytes) + } + + fn encrypt(&self, mut state: Vec) -> Result> { + let state_key = self.state_key_repository.retrieve_key()?; + + state_key + .encrypt(&mut state) + .map_err(|e| Error::Other(format!("{:?}", e).into()))?; + Ok(state) + } + } + + impl StateFileIo for SgxStateFileIo + where + StateKeyRepository: AccessKey, + ::KeyType: StateCrypto, + State: SgxExternalitiesTrait + Hash + Debug, + ::SgxExternalitiesType: Encode + Decode, + { + type StateType = State; + type HashType = H256; + + fn load( + &self, + shard_identifier: &ShardIdentifier, + state_id: StateId, + ) -> Result { + if !file_for_state_exists(shard_identifier, state_id) { + return Err(Error::InvalidStateId(state_id)) + } + + let state_path = state_file_path(shard_identifier, state_id); + trace!("loading state from: {:?}", state_path); + let state_encoded = self.read(&state_path)?; + + // State is now decrypted. + debug!( + "State loaded from {:?} with size {}B, deserializing...", + state_path, + state_encoded.len() + ); + let state = ::SgxExternalitiesType::decode( + &mut state_encoded.as_slice(), + )?; + + trace!("state decoded successfully"); + // Add empty state-diff. + let state_with_diff = State::new(state); + trace!("New state created: {:?}", state_with_diff); + Ok(state_with_diff) + } + + fn compute_hash( + &self, + shard_identifier: &ShardIdentifier, + state_id: StateId, + ) -> Result { + let state = self.load(shard_identifier, state_id)?; + Ok(state.hash()) + } + + fn initialize_shard( + &self, + shard_identifier: &ShardIdentifier, + state_id: StateId, + state: &Self::StateType, + ) -> Result { + init_shard(&shard_identifier)?; + self.write(shard_identifier, state_id, state) + } + + /// Writes the state (without the state diff) encrypted into the enclave storage. + /// Returns the hash of the saved state (independent of the diff!). + fn write( + &self, + shard_identifier: &ShardIdentifier, + state_id: StateId, + state: &Self::StateType, + ) -> Result { + let state_path = state_file_path(shard_identifier, state_id); + trace!("writing state to: {:?}", state_path); + + // Only save the state, the state diff is pruned. + let cyphertext = self.encrypt(state.state().encode())?; + + let state_hash = state.hash(); + + io_write(&cyphertext, &state_path)?; + + Ok(state_hash) + } + + fn remove(&self, shard_identifier: &ShardIdentifier, state_id: StateId) -> Result<()> { + fs::remove_file(state_file_path(shard_identifier, state_id)) + .map_err(|e| Error::Other(e.into())) + } + + fn shard_exists(&self, shard_identifier: &ShardIdentifier) -> bool { + shard_exists(shard_identifier) + } + + fn list_shards(&self) -> Result> { + list_shards() + } + + fn list_state_ids_for_shard( + &self, + shard_identifier: &ShardIdentifier, + ) -> Result> { + let shard_path = shard_path(shard_identifier); + let directory_items = list_items_in_directory(&shard_path); + + Ok(directory_items + .iter() + .flat_map(|item| { + let maybe_state_id = extract_state_id_from_file_name(item.as_str()); + if maybe_state_id.is_none() { + warn!("Found item ({}) that does not match state snapshot naming pattern, ignoring it", item) + } + maybe_state_id + }) + .collect()) + } + } + + fn state_file_path(shard: &ShardIdentifier, state_id: StateId) -> PathBuf { + let mut shard_file_path = shard_path(shard); + shard_file_path.push(to_file_name(state_id)); + shard_file_path + } + + fn file_for_state_exists(shard: &ShardIdentifier, state_id: StateId) -> bool { + state_file_path(shard, state_id).exists() + } + + /// Returns true if a shard directory for a given identifier exists AND contains at least one state file. + pub(crate) fn shard_exists(shard: &ShardIdentifier) -> bool { + let shard_path = shard_path(shard); + if !shard_path.exists() { + return false + } + + shard_path + .read_dir() + // When the iterator over all files in the directory returns none, the directory is empty. + .map(|mut d| d.next().is_some()) + .unwrap_or(false) + } + + pub(crate) fn init_shard(shard: &ShardIdentifier) -> Result<()> { + let path = shard_path(shard); + fs::create_dir_all(path).map_err(|e| Error::Other(e.into())) + } + + /// List any valid shards that are found in the shard path. + /// Ignore any items (files, directories) that are not valid shard identifiers. + pub(crate) fn list_shards() -> Result> { + let directory_items = list_items_in_directory(&PathBuf::from(format!("./{}", SHARDS_PATH))); + Ok(directory_items + .iter() + .flat_map(|item| { + item.from_base58() + .ok() + .map(|encoded_shard_id| { + ShardIdentifier::decode(&mut encoded_shard_id.as_slice()).ok() + }) + .flatten() + }) + .collect()) + } + + fn list_items_in_directory(directory: &Path) -> Vec { + let items = match directory.read_dir() { + Ok(rd) => rd, + Err(_) => return Vec::new(), + }; + + items + .flat_map(|fr| fr.map(|de| de.file_name().into_string().ok()).ok().flatten()) + .collect() + } +} + +/// Remove a shard directory with all of its content. +pub fn purge_shard_dir(shard: &ShardIdentifier) { + let shard_dir_path = shard_path(shard); + if let Err(e) = std::fs::remove_dir_all(&shard_dir_path) { + error!("Failed to remove shard directory {:?}: {:?}", shard_dir_path, e); + } +} + +pub(crate) fn shard_path(shard: &ShardIdentifier) -> PathBuf { + PathBuf::from(format!("./{}/{}", SHARDS_PATH, shard.encode().to_base58())) +} + +#[cfg(any(test, feature = "sgx"))] +fn to_file_name(state_id: StateId) -> String { + format!("{}_{}", state_id, ENCRYPTED_STATE_FILE) +} + +#[cfg(any(test, feature = "sgx"))] +fn extract_state_id_from_file_name(file_name: &str) -> Option { + let state_id_str = file_name.strip_suffix(format!("_{}", ENCRYPTED_STATE_FILE).as_str())?; + state_id_str.parse::().ok() +} + +#[cfg(test)] +mod tests { + + use super::*; + use crate::state_snapshot_primitives::generate_current_timestamp_state_id; + + #[test] + fn state_id_to_file_name_works() { + assert!(to_file_name(generate_current_timestamp_state_id()).ends_with(ENCRYPTED_STATE_FILE)); + assert!(to_file_name(generate_current_timestamp_state_id()) + .strip_suffix(format!("_{}", ENCRYPTED_STATE_FILE).as_str()) + .is_some()); + + let now_time_stamp = generate_current_timestamp_state_id(); + assert_eq!( + extract_state_id_from_file_name(to_file_name(now_time_stamp).as_str()).unwrap(), + now_time_stamp + ); + } + + #[test] + fn extract_timestamp_from_file_name_works() { + assert_eq!( + 123456u128, + extract_state_id_from_file_name(format!("123456_{}", ENCRYPTED_STATE_FILE).as_str()) + .unwrap() + ); + assert_eq!( + 0u128, + extract_state_id_from_file_name(format!("0_{}", ENCRYPTED_STATE_FILE).as_str()) + .unwrap() + ); + + assert!(extract_state_id_from_file_name( + format!("987345{}", ENCRYPTED_STATE_FILE).as_str() + ) + .is_none()); + assert!( + extract_state_id_from_file_name(format!("{}", ENCRYPTED_STATE_FILE).as_str()).is_none() + ); + assert!(extract_state_id_from_file_name( + format!("1234_{}-other", ENCRYPTED_STATE_FILE).as_str() + ) + .is_none()); + } +} diff --git a/tee-worker/core-primitives/stf-state-handler/src/handle_state.rs b/tee-worker/core-primitives/stf-state-handler/src/handle_state.rs new file mode 100644 index 0000000000..c6b8702610 --- /dev/null +++ b/tee-worker/core-primitives/stf-state-handler/src/handle_state.rs @@ -0,0 +1,67 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +#[cfg(feature = "sgx")] +use std::sync::SgxRwLockWriteGuard as RwLockWriteGuard; + +#[cfg(feature = "std")] +use std::sync::RwLockWriteGuard; + +use crate::error::Result; +use itp_types::ShardIdentifier; + +/// Facade for handling STF state loading and storing (e.g. from file). +pub trait HandleState { + type WriteLockPayload; + type StateT; + type HashType; + + /// Initialize a new shard. + /// + /// Initializes a default state for the shard and returns its hash. + fn initialize_shard(&self, shard: ShardIdentifier) -> Result; + + /// Load the state for a given shard. + /// + /// Requires the shard to exist and be initialized, otherwise returns an error. + fn load(&self, shard: &ShardIdentifier) -> Result; + + /// Load the state in order to mutate it. + /// + /// Returns a write lock to protect against any concurrent access as long as + /// the lock is held. Finalize the operation by calling `write` and returning + /// the lock again. + fn load_for_mutation( + &self, + shard: &ShardIdentifier, + ) -> Result<(RwLockWriteGuard<'_, Self::WriteLockPayload>, Self::StateT)>; + + /// Writes the state (without the state diff) encrypted into the enclave. + /// + /// Returns the hash of the saved state (independent of the diff!). + fn write_after_mutation( + &self, + state: Self::StateT, + state_lock: RwLockWriteGuard<'_, Self::WriteLockPayload>, + shard: &ShardIdentifier, + ) -> Result; + + /// Reset (or override) a state. + /// + /// Use in cases where the previous state is of no interest. Otherwise use `load_for_mutation` and `write_after_mutation`. + fn reset(&self, state: Self::StateT, shard: &ShardIdentifier) -> Result; +} diff --git a/tee-worker/core-primitives/stf-state-handler/src/in_memory_state_file_io.rs b/tee-worker/core-primitives/stf-state-handler/src/in_memory_state_file_io.rs new file mode 100644 index 0000000000..c979352db4 --- /dev/null +++ b/tee-worker/core-primitives/stf-state-handler/src/in_memory_state_file_io.rs @@ -0,0 +1,415 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +#[cfg(feature = "sgx")] +use std::sync::SgxRwLock as RwLock; + +#[cfg(feature = "std")] +use std::sync::RwLock; + +use crate::{ + error::{Error, Result}, + file_io::StateFileIo, + state_snapshot_primitives::StateId, +}; +use codec::Encode; +use itp_sgx_externalities::{SgxExternalities, SgxExternalitiesType}; +use itp_types::{ShardIdentifier, H256}; +use sp_core::blake2_256; +use std::{boxed::Box, collections::HashMap, sync::Arc, vec::Vec}; + +type StateHash = H256; +type ShardDirectory = HashMap; +type ShardsRootDirectory = HashMap>; +type InnerStateSelector = + Box State + Send + Sync + 'static>; +type ExternalStateGenerator = + Box ExternalState + Send + Sync + 'static>; + +/// State file I/O using (unencrypted) in-memory representation of the state files. +/// Can be used as mock for testing. +pub struct InMemoryStateFileIo +where + State: Clone + Default + Encode, +{ + emulated_shard_directory: RwLock>, + state_selector: InnerStateSelector, + external_state_generator: ExternalStateGenerator, +} + +impl InMemoryStateFileIo +where + State: Clone + Default + Encode, +{ + #[allow(unused)] + pub fn new( + shards: &[ShardIdentifier], + state_selector: InnerStateSelector, + external_state_generator: ExternalStateGenerator, + ) -> Self { + let shard_hash_map: HashMap<_, _> = + shards.iter().map(|s| (*s, ShardDirectory::::default())).collect(); + + InMemoryStateFileIo { + emulated_shard_directory: RwLock::new(shard_hash_map), + state_selector, + external_state_generator, + } + } + + #[cfg(any(test, feature = "test"))] + pub fn get_states_for_shard( + &self, + shard_identifier: &ShardIdentifier, + ) -> Result> { + let files_lock = self.emulated_shard_directory.read().map_err(|_| Error::LockPoisoning)?; + files_lock + .get(shard_identifier) + .cloned() + .ok_or_else(|| Error::InvalidShard(*shard_identifier)) + } + + fn compute_state_hash(&self, state: &State) -> StateHash { + let encoded_state = state.encode(); + blake2_256(&encoded_state).into() + } + + fn generate_state_entry(&self, state: State) -> (StateHash, State) { + let state_hash = self.compute_state_hash(&state); + (state_hash, state) + } +} + +impl StateFileIo for InMemoryStateFileIo +where + State: Clone + Default + Encode, +{ + type StateType = ExternalState; + type HashType = StateHash; + + fn load( + &self, + shard_identifier: &ShardIdentifier, + state_id: StateId, + ) -> Result { + let directory_lock = + self.emulated_shard_directory.read().map_err(|_| Error::LockPoisoning)?; + let states_for_shard = directory_lock + .get(shard_identifier) + .ok_or_else(|| Error::InvalidShard(*shard_identifier))?; + let inner_state = states_for_shard + .get(&state_id) + .map(|(_, s)| -> State { s.clone() }) + .ok_or_else(|| Error::InvalidStateId(state_id))?; + + Ok((self.external_state_generator)(inner_state)) + } + + fn compute_hash( + &self, + shard_identifier: &ShardIdentifier, + state_id: StateId, + ) -> Result { + let state = self.load(shard_identifier, state_id)?; + Ok(self.compute_state_hash(&(self.state_selector)(&state))) + } + + fn initialize_shard( + &self, + shard_identifier: &ShardIdentifier, + state_id: StateId, + external_state: &Self::StateType, + ) -> Result { + let mut directory_lock = + self.emulated_shard_directory.write().map_err(|_| Error::LockPoisoning)?; + + let states_for_shard = directory_lock.entry(*shard_identifier).or_default(); + let state_entry = states_for_shard + .entry(state_id) + .or_insert_with(|| self.generate_state_entry((self.state_selector)(external_state))); + Ok(state_entry.0) + } + + fn write( + &self, + shard_identifier: &ShardIdentifier, + state_id: StateId, + external_state: &Self::StateType, + ) -> Result { + let mut directory_lock = + self.emulated_shard_directory.write().map_err(|_| Error::LockPoisoning)?; + + let states_for_shard = directory_lock.entry(*shard_identifier).or_default(); + + let inner_state = (self.state_selector)(external_state); + let state_hash = self.compute_state_hash(&inner_state); + + *states_for_shard.entry(state_id).or_default() = (state_hash, inner_state); + + Ok(state_hash) + } + + fn remove(&self, shard_identifier: &ShardIdentifier, state_id: StateId) -> Result<()> { + let mut directory_lock = + self.emulated_shard_directory.write().map_err(|_| Error::LockPoisoning)?; + + let states_for_shard = directory_lock + .get_mut(shard_identifier) + .ok_or_else(|| Error::InvalidShard(*shard_identifier))?; + + states_for_shard + .remove(&state_id) + .ok_or_else(|| Error::InvalidStateId(state_id)) + .map(|_| {}) + } + + fn shard_exists(&self, shard_identifier: &ShardIdentifier) -> bool { + let directory_lock = self.emulated_shard_directory.read().unwrap(); + directory_lock.contains_key(shard_identifier) + } + + fn list_shards(&self) -> Result> { + let directory_lock = + self.emulated_shard_directory.read().map_err(|_| Error::LockPoisoning)?; + Ok(directory_lock.keys().copied().collect()) + } + + fn list_state_ids_for_shard(&self, shard_identifier: &ShardIdentifier) -> Result> { + let directory_lock = + self.emulated_shard_directory.read().map_err(|_| Error::LockPoisoning)?; + let shard_directory = directory_lock + .get(shard_identifier) + .ok_or_else(|| Error::InvalidShard(*shard_identifier))?; + Ok(shard_directory.keys().cloned().collect()) + } +} + +pub fn create_sgx_externalities_in_memory_state_io( +) -> Arc> { + create_in_memory_externalities_state_io(&[]) +} + +fn create_in_memory_externalities_state_io( + shards: &[ShardIdentifier], +) -> Arc> { + Arc::new(InMemoryStateFileIo::new( + shards, + sgx_externalities_selector(), + sgx_externalities_wrapper(), + )) +} + +fn sgx_externalities_selector() -> InnerStateSelector { + Box::new(|s| s.state.clone()) +} + +fn sgx_externalities_wrapper() -> ExternalStateGenerator { + Box::new(|s| SgxExternalities { state: s, state_diff: Default::default() }) +} + +#[cfg(feature = "sgx")] +pub mod sgx { + use super::*; + use crate::file_io::sgx::list_shards; + + pub fn create_in_memory_state_io_from_shards_directories( + ) -> Result>> { + let shards = list_shards()?; + Ok(create_in_memory_externalities_state_io(&shards)) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use std::assert_matches::assert_matches; + + type TestState = u64; + type TestStateFileIo = InMemoryStateFileIo; + + #[test] + fn shard_directory_is_empty_after_initialization() { + let state_file_io = create_empty_in_memory_state_file_io(); + assert!(state_file_io.list_shards().unwrap().is_empty()); + } + + #[test] + fn load_on_empty_directory_and_shard_returns_error() { + let state_file_io = create_empty_in_memory_state_file_io(); + + assert_matches!( + state_file_io.load(&ShardIdentifier::random(), 1234), + Err(Error::InvalidShard(_)) + ); + } + + #[test] + fn initialize_with_shard_creates_empty_directory() { + let shard = ShardIdentifier::from([2u8; 32]); + let state_file_io = create_in_memory_state_file_io(&[shard]); + + assert!(state_file_io.list_state_ids_for_shard(&shard).unwrap().is_empty()); + assert!(state_file_io + .list_state_ids_for_shard(&ShardIdentifier::from([3u8; 32])) + .is_err()); + } + + #[test] + fn load_when_state_does_not_exist_returns_error() { + let state_file_io = create_empty_in_memory_state_file_io(); + let shard_id = ShardIdentifier::random(); + let _ = state_file_io.initialize_shard(&shard_id, 1234, &Default::default()).unwrap(); + + assert_matches!(state_file_io.load(&shard_id, 12345), Err(Error::InvalidStateId(12345))); + } + + #[test] + fn create_initialized_when_shard_already_exists_works() { + let shard = ShardIdentifier::random(); + let state_file_io = create_in_memory_state_file_io(&[shard]); + + assert!(state_file_io.initialize_shard(&shard, 1245, &Default::default()).is_ok()); + } + + #[test] + fn create_initialized_adds_default_state() { + let state_file_io = create_empty_in_memory_state_file_io(); + let shard_id = ShardIdentifier::random(); + let state_id = 31081984u128; + let state_hash = state_file_io + .initialize_shard(&shard_id, state_id, &Default::default()) + .unwrap(); + + assert_eq!(1, state_file_io.list_shards().unwrap().len()); + assert_eq!(TestState::default(), state_file_io.load(&shard_id, state_id).unwrap()); + assert_eq!(1, state_file_io.list_state_ids_for_shard(&shard_id).unwrap().len()); + + assert_entry(&state_file_io, &shard_id, state_id, &TestState::default(), &state_hash); + } + + #[test] + fn write_works_when_no_previous_shard_or_file_exists() { + let state_file_io = create_empty_in_memory_state_file_io(); + let shard_id = ShardIdentifier::random(); + let state_id = 23u128; + let test_state = 42u64; + + let state_hash = state_file_io.write(&shard_id, state_id, &test_state).unwrap(); + + assert_eq!(1, state_file_io.list_shards().unwrap().len()); + assert_eq!(test_state, state_file_io.load(&shard_id, state_id).unwrap()); + assert_eq!(1, state_file_io.list_state_ids_for_shard(&shard_id).unwrap().len()); + assert_entry(&state_file_io, &shard_id, state_id, &test_state, &state_hash); + } + + #[test] + fn write_overwrites_existing_state() { + let state_file_io = create_empty_in_memory_state_file_io(); + let shard_id = ShardIdentifier::random(); + let state_id = 123456u128; + let _ = state_file_io + .initialize_shard(&shard_id, state_id, &Default::default()) + .unwrap(); + + let test_state = 4256u64; + let state_hash = state_file_io.write(&shard_id, state_id, &test_state).unwrap(); + + assert_eq!(1, state_file_io.list_shards().unwrap().len()); + assert_eq!(test_state, state_file_io.load(&shard_id, state_id).unwrap()); + assert_eq!(1, state_file_io.list_state_ids_for_shard(&shard_id).unwrap().len()); + assert_entry(&state_file_io, &shard_id, state_id, &test_state, &state_hash); + } + + #[test] + fn remove_files_works() { + let state_file_io = create_empty_in_memory_state_file_io(); + let shard_id = ShardIdentifier::random(); + let initial_state_id = 42u128; + let _ = state_file_io + .initialize_shard(&shard_id, initial_state_id, &Default::default()) + .unwrap(); + + let state_ids = vec![1u128, 2u128, 3u128]; + + for state_id in state_ids.iter() { + let _ = state_file_io.write(&shard_id, *state_id, &987345).unwrap(); + } + + let mut expected_size = state_ids.len() + 1; + assert_eq!(expected_size, state_file_io.list_state_ids_for_shard(&shard_id).unwrap().len()); + expected_size -= 1; + + for state_id in state_ids.iter() { + state_file_io.remove(&shard_id, *state_id).unwrap(); + assert_matches!( + state_file_io.load(&shard_id, *state_id), + Err(Error::InvalidStateId(_)) + ); + assert_eq!( + expected_size, + state_file_io.list_state_ids_for_shard(&shard_id).unwrap().len() + ); + expected_size -= 1; + } + } + + #[test] + fn initialize_with_shards_creates_empty_maps() { + let shards = vec![ShardIdentifier::random(), ShardIdentifier::random()]; + let state_file_io = create_in_memory_state_file_io(shards.as_slice()); + + assert_eq!(shards.len(), state_file_io.list_shards().unwrap().len()); + for shard in shards { + assert!(state_file_io.list_state_ids_for_shard(&shard).unwrap().is_empty()); + } + } + + fn assert_entry( + state_file_io: &TestStateFileIo, + shard_id: &ShardIdentifier, + state_id: StateId, + state: &TestState, + state_hash: &StateHash, + ) { + let (retrieved_hash, retrieved_state) = + get_state_entry(&state_file_io, &shard_id, state_id); + assert!(state_file_io.shard_exists(shard_id)); + assert_eq!(state_hash, &retrieved_hash); + assert_eq!(state, &retrieved_state); + } + + fn get_state_entry( + state_file_io: &TestStateFileIo, + shard_id: &ShardIdentifier, + state_id: StateId, + ) -> (StateHash, TestState) { + state_file_io + .get_states_for_shard(shard_id) + .unwrap() + .get(&state_id) + .unwrap() + .clone() + } + + fn create_in_memory_state_file_io(shards: &[ShardIdentifier]) -> TestStateFileIo { + InMemoryStateFileIo::new(shards, Box::new(|x| *x), Box::new(|x| x)) + } + + fn create_empty_in_memory_state_file_io() -> TestStateFileIo { + create_in_memory_state_file_io(&[]) + } +} diff --git a/tee-worker/core-primitives/stf-state-handler/src/lib.rs b/tee-worker/core-primitives/stf-state-handler/src/lib.rs new file mode 100644 index 0000000000..4b6235f9c0 --- /dev/null +++ b/tee-worker/core-primitives/stf-state-handler/src/lib.rs @@ -0,0 +1,46 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +#![cfg_attr(not(feature = "std"), no_std)] +#![feature(assert_matches)] + +#[cfg(all(feature = "std", feature = "sgx"))] +compile_error!("feature \"std\" and feature \"sgx\" cannot be enabled at the same time"); + +#[cfg(all(not(feature = "std"), feature = "sgx"))] +extern crate sgx_tstd as std; + +// re-export module to properly feature gate sgx and regular std environment +#[cfg(all(not(feature = "std"), feature = "sgx"))] +pub mod sgx_reexport_prelude { + pub use rust_base58_sgx as base58; + pub use thiserror_sgx as thiserror; +} + +pub mod error; +pub mod file_io; +pub mod handle_state; +pub mod in_memory_state_file_io; +pub mod query_shard_state; +pub mod state_handler; +pub mod state_initializer; +mod state_snapshot_primitives; +pub mod state_snapshot_repository; +pub mod state_snapshot_repository_loader; +pub mod test; + +pub use state_handler::StateHandler; diff --git a/tee-worker/core-primitives/stf-state-handler/src/query_shard_state.rs b/tee-worker/core-primitives/stf-state-handler/src/query_shard_state.rs new file mode 100644 index 0000000000..11ff46d044 --- /dev/null +++ b/tee-worker/core-primitives/stf-state-handler/src/query_shard_state.rs @@ -0,0 +1,32 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +use crate::error::Result; +use itp_types::ShardIdentifier; +use std::vec::Vec; + +/// Trait for querying shard information on the state +/// +/// The reason this is a separate trait, is that it does not require any +/// SGX exclusive data structures (feature sgx) +pub trait QueryShardState { + /// Query whether a given shard exists + fn shard_exists(&self, shard: &ShardIdentifier) -> Result; + + /// List all available shards + fn list_shards(&self) -> Result>; +} diff --git a/tee-worker/core-primitives/stf-state-handler/src/state_handler.rs b/tee-worker/core-primitives/stf-state-handler/src/state_handler.rs new file mode 100644 index 0000000000..4c5a1ddfbe --- /dev/null +++ b/tee-worker/core-primitives/stf-state-handler/src/state_handler.rs @@ -0,0 +1,397 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +#[cfg(feature = "sgx")] +use std::sync::{SgxRwLock as RwLock, SgxRwLockWriteGuard as RwLockWriteGuard}; + +#[cfg(feature = "std")] +use std::sync::{RwLock, RwLockWriteGuard}; + +use crate::{ + error::{Error, Result}, + handle_state::HandleState, + query_shard_state::QueryShardState, + state_initializer::InitializeState, + state_snapshot_repository::VersionedStateAccess, +}; +use itp_hashing::Hash; +use itp_sgx_externalities::SgxExternalitiesTrait; +use itp_stf_state_observer::traits::UpdateState; +use itp_types::ShardIdentifier; +use std::{collections::HashMap, sync::Arc, vec::Vec}; + +type StatesMap = HashMap; + +/// Implementation of the `HandleState` trait. +/// +/// Responsible for handling any state instances. Holds a map with all the latest states for each shard. +/// In addition, uses the snapshot repository to save file snapshots of a state. +pub struct StateHandler +where + Repository: VersionedStateAccess, +{ + state_snapshot_repository: RwLock, + states_map_lock: RwLock>, + state_observer: Arc, + state_initializer: Arc, +} + +impl + StateHandler +where + Repository: VersionedStateAccess, + Repository::StateType: Hash, + StateObserver: UpdateState, + StateInitializer: InitializeState, +{ + /// Creates a new instance WITHOUT loading any state from the repository. + /// Results in an empty states map. + pub fn new( + state_snapshot_repository: Repository, + state_observer: Arc, + state_initializer: Arc, + ) -> Self { + Self::new_with_states_map( + state_snapshot_repository, + state_observer, + state_initializer, + Default::default(), + ) + } + + /// Create a new state handler and initialize its state map with the + /// states that are available in the snapshot repository. + pub fn load_from_repository( + state_snapshot_repository: Repository, + state_observer: Arc, + state_initializer: Arc, + ) -> Result { + let states_map = Self::load_all_latest_snapshots(&state_snapshot_repository)?; + Ok(Self::new_with_states_map( + state_snapshot_repository, + state_observer, + state_initializer, + states_map, + )) + } + + fn new_with_states_map( + state_snapshot_repository: Repository, + state_observer: Arc, + state_initializer: Arc, + states_map: StatesMap, + ) -> Self { + StateHandler { + state_snapshot_repository: RwLock::new(state_snapshot_repository), + states_map_lock: RwLock::new(states_map), + state_observer, + state_initializer, + } + } + + fn load_all_latest_snapshots( + state_snapshot_repository: &Repository, + ) -> Result> { + let shards = state_snapshot_repository.list_shards()?; + + let r = shards + .into_iter() + .map(|shard| state_snapshot_repository.load_latest(&shard).map(|state| (state, shard))) + // Fill the pairs for state and shard into a map. + // Log an error for cases where state could not be loaded. + .fold(StatesMap::default(), |mut map, x| { + match x { + Ok((state, shard)) => { + let state_hash = state.hash(); + map.insert(shard, (state, state_hash)); + }, + Err(e) => { + log::error!("Failed to load state from snapshot repository {:?}", e); + }, + }; + map + }); + + Ok(r) + } + + fn update_state_snapshot( + &self, + shard: &ShardIdentifier, + state: &Repository::StateType, + state_hash: Repository::HashType, + ) -> Result<()> { + let mut state_snapshots_lock = + self.state_snapshot_repository.write().map_err(|_| Error::LockPoisoning)?; + + state_snapshots_lock.update(shard, state, state_hash) + } +} + +impl HandleState + for StateHandler +where + Repository: VersionedStateAccess, + Repository::StateType: SgxExternalitiesTrait + Hash, + Repository::HashType: Copy, + StateObserver: UpdateState, + StateInitializer: InitializeState, +{ + type WriteLockPayload = StatesMap; + type StateT = Repository::StateType; + type HashType = Repository::HashType; + + fn initialize_shard(&self, shard: ShardIdentifier) -> Result { + let initialized_state = self.state_initializer.initialize()?; + self.reset(initialized_state, &shard) + } + + fn load(&self, shard: &ShardIdentifier) -> Result { + let state = self + .states_map_lock + .read() + .map_err(|_| Error::LockPoisoning)? + .get(shard) + .ok_or_else(|| Error::InvalidShard(*shard))? + .0 + .clone(); + + Ok(state) + } + + fn load_for_mutation( + &self, + shard: &ShardIdentifier, + ) -> Result<(RwLockWriteGuard<'_, Self::WriteLockPayload>, Self::StateT)> { + let state_write_lock = self.states_map_lock.write().map_err(|_| Error::LockPoisoning)?; + let state_clone = state_write_lock + .get(shard) + .ok_or_else(|| Error::InvalidShard(*shard))? + .0 + .clone(); + + Ok((state_write_lock, state_clone)) + } + + fn write_after_mutation( + &self, + mut state: Self::StateT, + mut state_lock: RwLockWriteGuard<'_, Self::WriteLockPayload>, + shard: &ShardIdentifier, + ) -> Result { + state.prune_state_diff(); // Remove state diff before storing. + let state_hash = state.hash(); + // We create a state copy here, in order to serve the state observer. This does not scale + // well and we will want a better solution in the future, maybe with #459. + state_lock.insert(*shard, (state.clone(), state_hash)); + drop(state_lock); // Drop the write lock as early as possible. + + self.update_state_snapshot(shard, &state, state_hash)?; + + self.state_observer.queue_state_update(*shard, state)?; + Ok(state_hash) + } + + fn reset(&self, state: Self::StateT, shard: &ShardIdentifier) -> Result { + let state_write_lock = self.states_map_lock.write().map_err(|_| Error::LockPoisoning)?; + self.write_after_mutation(state, state_write_lock, shard) + } +} + +impl QueryShardState + for StateHandler +where + Repository: VersionedStateAccess, + Repository::StateType: Hash, + StateObserver: UpdateState, + StateInitializer: InitializeState, +{ + fn shard_exists(&self, shard: &ShardIdentifier) -> Result { + let states_map_lock = self.states_map_lock.read().map_err(|_| Error::LockPoisoning)?; + Ok(states_map_lock.contains_key(shard)) + } + + fn list_shards(&self) -> Result> { + let states_map_lock = self.states_map_lock.read().map_err(|_| Error::LockPoisoning)?; + Ok(states_map_lock.keys().cloned().collect()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::test::mocks::{ + initialize_state_mock::InitializeStateMock, + versioned_state_access_mock::VersionedStateAccessMock, + }; + use codec::Encode; + use itp_sgx_externalities::{SgxExternalities, SgxExternalitiesType}; + use itp_stf_state_observer::mock::UpdateStateMock; + use itp_types::H256; + use std::{collections::VecDeque, sync::Arc, thread}; + + type TestState = SgxExternalities; + type TestHash = H256; + type TestStateRepository = VersionedStateAccessMock; + type TestStateObserver = UpdateStateMock; + type TestStateInitializer = InitializeStateMock; + type TestStateHandler = + StateHandler; + + fn create_state(content: u64) -> TestState { + let mut state = TestState::new(SgxExternalitiesType::default()); + state.insert("key_1".encode(), content.encode()); + state + } + + fn create_state_without_diff(content: u64) -> TestState { + let state = create_state(content); + prune_diff(state) + } + + fn prune_diff(mut state: TestState) -> TestState { + state.prune_state_diff(); + state + } + + #[test] + fn load_for_mutation_blocks_any_concurrent_access() { + let shard_id = ShardIdentifier::random(); + let state_handler = default_state_handler(); + state_handler.initialize_shard(shard_id).unwrap(); + + let (lock, _s) = state_handler.load_for_mutation(&shard_id).unwrap(); + + let state_handler_clone = state_handler.clone(); + let join_handle = thread::spawn(move || { + let latest_state = state_handler_clone.load(&shard_id).unwrap(); + assert_eq!(create_state_without_diff(4u64), latest_state); + }); + + let _hash = + state_handler.write_after_mutation(create_state(4u64), lock, &shard_id).unwrap(); + + join_handle.join().unwrap(); + } + + #[test] + fn write_and_reset_queue_observer_update() { + let shard_id = ShardIdentifier::default(); + let state_observer = Arc::new(TestStateObserver::default()); + let state_initializer = Arc::new(TestStateInitializer::new(Default::default())); + let state_handler = Arc::new(TestStateHandler::new( + default_repository(), + state_observer.clone(), + state_initializer, + )); + state_handler.initialize_shard(shard_id).unwrap(); + + let (lock, _s) = state_handler.load_for_mutation(&shard_id).unwrap(); + let new_state = create_state(4u64); + state_handler.write_after_mutation(new_state.clone(), lock, &shard_id).unwrap(); + + let reset_state = create_state(5u64); + state_handler.reset(reset_state.clone(), &shard_id).unwrap(); + + let observer_updates = state_observer.queued_updates.read().unwrap().clone(); + assert_eq!(3, observer_updates.len()); + assert_eq!((shard_id, prune_diff(new_state)), observer_updates[1]); + assert_eq!((shard_id, prune_diff(reset_state)), observer_updates[2]); + } + + #[test] + fn load_initialized_works() { + let shard_id = ShardIdentifier::random(); + let state_handler = default_state_handler(); + state_handler.initialize_shard(shard_id).unwrap(); + assert!(state_handler.load(&shard_id).is_ok()); + assert!(state_handler.load(&ShardIdentifier::random()).is_err()); + } + + #[test] + fn list_shards_works() { + let shard_id = ShardIdentifier::random(); + let state_handler = default_state_handler(); + state_handler.initialize_shard(shard_id).unwrap(); + assert_eq!(1, state_handler.list_shards().unwrap().len()); + } + + #[test] + fn shard_exists_works() { + let shard_id = ShardIdentifier::random(); + let state_handler = default_state_handler(); + state_handler.initialize_shard(shard_id).unwrap(); + assert!(state_handler.shard_exists(&shard_id).unwrap()); + assert!(!state_handler.shard_exists(&ShardIdentifier::random()).unwrap()); + } + + #[test] + fn load_from_repository_works() { + let state_observer = Arc::new(TestStateObserver::default()); + let state_initializer = Arc::new(TestStateInitializer::new(Default::default())); + + let repository = TestStateRepository::new(HashMap::from([ + ( + ShardIdentifier::from([1u8; 32]), + VecDeque::from([create_state(3), create_state(2), create_state(1)]), + ), + (ShardIdentifier::from([2u8; 32]), VecDeque::from([create_state(5)])), + (ShardIdentifier::from([3u8; 32]), VecDeque::new()), + ])); + + assert_eq!(3, repository.list_shards().unwrap().len()); + assert!(repository.load_latest(&ShardIdentifier::from([3u8; 32])).is_err()); + + let state_handler = + TestStateHandler::load_from_repository(repository, state_observer, state_initializer) + .unwrap(); + + assert_eq!( + 2, + state_handler.list_shards().unwrap().len(), + "Only 2 shards, not 3, because 3rd was empty" + ); + } + + #[test] + fn ensure_state_diff_is_discarded() { + let shard_id = ShardIdentifier::random(); + let state_handler = default_state_handler(); + + let state = create_state(3u64); + let state_without_diff = { + let mut state_clone = state.clone(); + state_clone.prune_state_diff(); + state_clone + }; + + state_handler.reset(state, &shard_id).unwrap(); + let loaded_state = state_handler.load(&shard_id).unwrap(); + + assert_eq!(state_without_diff, loaded_state); + } + + fn default_state_handler() -> Arc { + let state_observer = Arc::new(TestStateObserver::default()); + let state_initializer = Arc::new(TestStateInitializer::new(Default::default())); + Arc::new(TestStateHandler::new(default_repository(), state_observer, state_initializer)) + } + + fn default_repository() -> TestStateRepository { + TestStateRepository::default() + } +} diff --git a/tee-worker/core-primitives/stf-state-handler/src/state_initializer.rs b/tee-worker/core-primitives/stf-state-handler/src/state_initializer.rs new file mode 100644 index 0000000000..5799c20823 --- /dev/null +++ b/tee-worker/core-primitives/stf-state-handler/src/state_initializer.rs @@ -0,0 +1,64 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +use crate::error::Result; +use core::marker::PhantomData; +use itp_sgx_crypto::{ed25519_derivation::DeriveEd25519, key_repository::AccessKey}; +use itp_stf_interface::InitState; +use itp_types::AccountId; +use sp_core::Pair; +use std::sync::Arc; + +/// Create and initialize a new state instance. +pub trait InitializeState { + type StateType; + + fn initialize(&self) -> Result; +} + +pub struct StateInitializer { + shielding_key_repository: Arc, + _phantom: PhantomData<(State, Stf)>, +} + +impl StateInitializer +where + Stf: InitState, + ShieldingKeyRepository: AccessKey, + ShieldingKeyRepository::KeyType: DeriveEd25519, +{ + pub fn new(shielding_key_repository: Arc) -> Self { + Self { shielding_key_repository, _phantom: Default::default() } + } +} + +impl InitializeState + for StateInitializer +where + Stf: InitState, + ShieldingKeyRepository: AccessKey, + ShieldingKeyRepository::KeyType: DeriveEd25519, +{ + type StateType = State; + + fn initialize(&self) -> Result { + // This implementation basically exists because it is non-trivial to initialize the state with + // an enclave account that is derived from the shielding key. + let enclave_account = self.shielding_key_repository.retrieve_key()?.derive_ed25519()?; + Ok(Stf::init_state(enclave_account.public().into())) + } +} diff --git a/tee-worker/core-primitives/stf-state-handler/src/state_snapshot_primitives.rs b/tee-worker/core-primitives/stf-state-handler/src/state_snapshot_primitives.rs new file mode 100644 index 0000000000..50c3f00afc --- /dev/null +++ b/tee-worker/core-primitives/stf-state-handler/src/state_snapshot_primitives.rs @@ -0,0 +1,56 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +use crate::{error::Result, file_io::StateFileIo}; +use itp_time_utils::now_as_nanos; +use itp_types::ShardIdentifier; +use std::collections::{HashMap, VecDeque}; + +pub type StateId = u128; + +pub(crate) type SnapshotHistory = + HashMap>>; + +/// Internal wrapper for a state hash and state ID. +#[derive(Clone)] +pub(crate) struct StateSnapshotMetaData { + pub(crate) state_hash: HashType, + pub(crate) state_id: StateId, +} + +impl StateSnapshotMetaData { + pub fn new(state_hash: HashType, state_id: StateId) -> Self { + StateSnapshotMetaData { state_hash, state_id } + } +} + +pub(crate) fn initialize_shard_with_snapshot( + shard_identifier: &ShardIdentifier, + file_io: &FileIo, + state: &FileIo::StateType, +) -> Result> +where + FileIo: StateFileIo, +{ + let state_id = generate_current_timestamp_state_id(); + let state_hash = file_io.initialize_shard(shard_identifier, state_id, state)?; + Ok(StateSnapshotMetaData::new(state_hash, state_id)) +} + +pub(crate) fn generate_current_timestamp_state_id() -> StateId { + now_as_nanos() +} diff --git a/tee-worker/core-primitives/stf-state-handler/src/state_snapshot_repository.rs b/tee-worker/core-primitives/stf-state-handler/src/state_snapshot_repository.rs new file mode 100644 index 0000000000..1b60a88741 --- /dev/null +++ b/tee-worker/core-primitives/stf-state-handler/src/state_snapshot_repository.rs @@ -0,0 +1,484 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +use crate::{ + error::{Error, Result}, + file_io::StateFileIo, + state_snapshot_primitives::{ + generate_current_timestamp_state_id, initialize_shard_with_snapshot, SnapshotHistory, + StateId, StateSnapshotMetaData, + }, +}; +use core::ops::RangeBounds; +use itp_types::ShardIdentifier; +use log::*; +use std::{collections::VecDeque, fmt::Debug, format, sync::Arc, vec::Vec}; + +/// Trait for versioned state access. Manages history of state snapshots. +pub trait VersionedStateAccess { + type StateType: Clone; + type HashType; + + /// Load the latest version of the state. + fn load_latest(&self, shard_identifier: &ShardIdentifier) -> Result; + + /// Update the state, returning the hash of the state. + fn update( + &mut self, + shard_identifier: &ShardIdentifier, + state: &Self::StateType, + state_hash: Self::HashType, + ) -> Result<()>; + + /// Reverts the state of a given shard to a state version identified by a state hash. + fn revert_to( + &mut self, + shard_identifier: &ShardIdentifier, + state_hash: &Self::HashType, + ) -> Result; + + /// Initialize a new shard. + /// + /// If the shard already exists, it will re-initialize it. + fn initialize_new_shard( + &mut self, + shard_identifier: ShardIdentifier, + state: &Self::StateType, + ) -> Result; + + /// Checks if a shard for a given identifier exists. + fn shard_exists(&self, shard_identifier: &ShardIdentifier) -> bool; + + /// Lists all shards. + fn list_shards(&self) -> Result>; +} + +/// State snapshot repository. +/// +/// Keeps versions of state snapshots, cycles them in a fixed-size circular buffer. +/// Creates a state snapshot for each write/update operation. Allows reverting to a specific snapshot, +/// identified by a state hash. Snapshot files names includes a timestamp to be unique. +pub struct StateSnapshotRepository +where + FileIo: StateFileIo, + ::HashType: Copy + Eq + Debug, + ::StateType: Clone, +{ + file_io: Arc, + snapshot_history_cache_size: usize, + snapshot_history: SnapshotHistory, +} + +impl StateSnapshotRepository +where + FileIo: StateFileIo, + ::HashType: Copy + Eq + Debug, + ::StateType: Clone, +{ + /// Constructor, initialized with no shards or snapshot history. + pub fn empty(file_io: Arc, snapshot_history_cache_size: usize) -> Result { + Self::new(file_io, snapshot_history_cache_size, SnapshotHistory::default()) + } + + /// Constructor to initialize the repository with shards and snapshot history. + /// + /// Crate private, to be used by the loader. + pub(crate) fn new( + file_io: Arc, + snapshot_history_cache_size: usize, + snapshot_history: SnapshotHistory, + ) -> Result { + if snapshot_history_cache_size == 0usize { + return Err(Error::ZeroCacheSize) + } + + Ok(StateSnapshotRepository { file_io, snapshot_history_cache_size, snapshot_history }) + } + + fn get_snapshot_history_mut( + &mut self, + shard_identifier: &ShardIdentifier, + ) -> Result<&mut VecDeque>> { + self.snapshot_history + .get_mut(shard_identifier) + .ok_or_else(|| Error::InvalidShard(*shard_identifier)) + } + + fn get_snapshot_history( + &self, + shard_identifier: &ShardIdentifier, + ) -> Result<&VecDeque>> { + self.snapshot_history + .get(shard_identifier) + .ok_or_else(|| Error::InvalidShard(*shard_identifier)) + } + + fn get_latest_snapshot_metadata( + &self, + shard_identifier: &ShardIdentifier, + ) -> Result<&StateSnapshotMetaData> { + let snapshot_history = self.get_snapshot_history(shard_identifier)?; + snapshot_history.front().ok_or(Error::EmptyRepository) + } + + fn prune_snapshot_history_by_range>( + &mut self, + shard_identifier: &ShardIdentifier, + range: R, + ) -> Result<()> { + let state_snapshots_to_remove = self + .get_snapshot_history_mut(shard_identifier)? + .drain(range) + .collect::>(); + + self.remove_snapshots(shard_identifier, state_snapshots_to_remove.as_slice()); + Ok(()) + } + + /// Remove snapshots referenced by metadata. + /// Does not stop on error, it's guaranteed to call `remove` on all elements. + /// Logs any errors that occur. + fn remove_snapshots( + &self, + shard_identifier: &ShardIdentifier, + snapshots_metadata: &[StateSnapshotMetaData], + ) { + for snapshot_metadata in snapshots_metadata { + if let Err(e) = self.file_io.remove(shard_identifier, snapshot_metadata.state_id) { + // We just log an error, don't want to return the error here, because the operation + // in general was successful, just a side-effect that failed. + error!("Failed to remove state, with id '{}': {:?}", snapshot_metadata.state_id, e); + } + } + } + + fn write_new_state( + &self, + shard_identifier: &ShardIdentifier, + state: &FileIo::StateType, + ) -> Result<(FileIo::HashType, StateId)> { + let state_id = generate_current_timestamp_state_id(); + let state_hash = self.file_io.write(shard_identifier, state_id, state)?; + Ok((state_hash, state_id)) + } + + fn initialize_shard_with_snapshot( + &mut self, + shard_identifier: &ShardIdentifier, + state: &FileIo::StateType, + ) -> Result { + let snapshot_metadata = + initialize_shard_with_snapshot(shard_identifier, self.file_io.as_ref(), state)?; + + let state_hash = snapshot_metadata.state_hash; + self.snapshot_history + .insert(*shard_identifier, VecDeque::from([snapshot_metadata])); + Ok(state_hash) + } + + fn load_state( + &self, + shard_identifier: &ShardIdentifier, + snapshot_metadata: &StateSnapshotMetaData, + ) -> Result { + self.file_io.load(shard_identifier, snapshot_metadata.state_id) + } +} + +impl VersionedStateAccess for StateSnapshotRepository +where + FileIo: StateFileIo, + ::HashType: Copy + Eq + Debug, + ::StateType: Clone, +{ + type StateType = FileIo::StateType; + type HashType = FileIo::HashType; + + fn load_latest(&self, shard_identifier: &ShardIdentifier) -> Result { + let latest_snapshot_metadata = self.get_latest_snapshot_metadata(shard_identifier)?; + self.file_io.load(shard_identifier, latest_snapshot_metadata.state_id) + } + + fn update( + &mut self, + shard_identifier: &ShardIdentifier, + state: &Self::StateType, + state_hash: Self::HashType, + ) -> Result<()> { + if !self.shard_exists(shard_identifier) { + self.initialize_shard_with_snapshot(shard_identifier, state)?; + return Ok(()) + } + + let (_state_hash, state_id) = self.write_new_state(shard_identifier, state)?; + let cache_size = self.snapshot_history_cache_size; + + let snapshot_history = self.get_snapshot_history_mut(shard_identifier)?; + snapshot_history.push_front(StateSnapshotMetaData::new(state_hash, state_id)); + + // In case we're above max queue size we remove the oldest entries and corresponding files + if snapshot_history.len() > cache_size { + self.prune_snapshot_history_by_range(shard_identifier, cache_size..)?; + } + + Ok(()) + } + + fn revert_to( + &mut self, + shard_identifier: &ShardIdentifier, + state_hash: &Self::HashType, + ) -> Result { + let snapshot_history = self.get_snapshot_history(shard_identifier)?; + + // We use `position()` instead of `find()`, because it then allows us to easily drain + // all the newer states. + let snapshot_metadata_index = snapshot_history + .iter() + .position(|fmd| fmd.state_hash == *state_hash) + .ok_or_else(|| Error::StateNotFoundInRepository(format!("{:?}", state_hash)))?; + + // Should never fail, since we got the index from above, with `position()`. + let snapshot_metadata = snapshot_history + .get(snapshot_metadata_index) + .ok_or_else(|| Error::StateNotFoundInRepository(format!("{:?}", state_hash)))?; + + let state = self.load_state(shard_identifier, snapshot_metadata)?; + + // Remove any state versions newer than the one we're resetting to + // (do this irreversible operation last, to ensure the loading has succeeded) + self.prune_snapshot_history_by_range(shard_identifier, ..snapshot_metadata_index)?; + + Ok(state) + } + + fn initialize_new_shard( + &mut self, + shard_identifier: ShardIdentifier, + state: &Self::StateType, + ) -> Result { + self.initialize_shard_with_snapshot(&shard_identifier, state) + } + + fn shard_exists(&self, shard_identifier: &ShardIdentifier) -> bool { + self.snapshot_history.get(shard_identifier).is_some() + } + + fn list_shards(&self) -> Result> { + Ok(self.snapshot_history.keys().cloned().collect()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::{ + in_memory_state_file_io::InMemoryStateFileIo, + state_snapshot_repository_loader::StateSnapshotRepositoryLoader, + test::mocks::initialize_state_mock::InitializeStateMock, + }; + use codec::Encode; + use itp_hashing::Hash; + use sp_core::{blake2_256, H256}; + use std::vec; + + #[derive(Encode, Clone, Default, Copy, Eq, PartialEq, Debug)] + struct TestState(pub u64); + + impl Hash for TestState { + fn hash(&self) -> H256 { + blake2_256(&self.encode()).into() + } + } + + type TestFileIo = InMemoryStateFileIo; + type TestStateInitializer = InitializeStateMock; + type TestSnapshotRepository = StateSnapshotRepository; + + const TEST_SNAPSHOT_REPOSITORY_CACHE_SIZE: usize = 3; + + #[test] + fn new_with_zero_cache_size_returns_error() { + let shards = + vec![ShardIdentifier::random(), ShardIdentifier::random(), ShardIdentifier::random()]; + let file_io = create_test_file_io(shards.as_slice()); + + assert!(TestSnapshotRepository::empty(file_io.clone(), 0usize).is_err()); + } + + #[test] + fn upon_new_all_shards_are_initialized() { + let shards = + vec![ShardIdentifier::random(), ShardIdentifier::random(), ShardIdentifier::random()]; + let (file_io, state_snapshot_repository) = create_state_snapshot_repository( + shards.as_slice(), + TEST_SNAPSHOT_REPOSITORY_CACHE_SIZE, + ); + + assert_eq!(shards.len(), file_io.list_shards().unwrap().len()); + assert_eq!(shards.len(), state_snapshot_repository.snapshot_history.len()); + assert_eq!(shards.len(), state_snapshot_repository.list_shards().unwrap().len()); + for states_per_shard in state_snapshot_repository.snapshot_history.values() { + assert_eq!(1, states_per_shard.len()); + } + for shard in shards { + assert!(state_snapshot_repository.load_latest(&shard).is_ok()); + assert!(state_snapshot_repository.shard_exists(&shard)); + } + } + + #[test] + fn update_latest_creates_new_state_file() { + let shards = + vec![ShardIdentifier::random(), ShardIdentifier::random(), ShardIdentifier::random()]; + let (file_io, mut state_snapshot_repository) = create_state_snapshot_repository( + shards.as_slice(), + TEST_SNAPSHOT_REPOSITORY_CACHE_SIZE, + ); + + let shard_to_update = shards.get(1).unwrap(); + assert_eq!(1, file_io.get_states_for_shard(shard_to_update).unwrap().len()); + + let new_state = TestState(1234u64); + + let _ = state_snapshot_repository + .update(shard_to_update, &new_state, Default::default()) + .unwrap(); + + let snapshot_history = + state_snapshot_repository.snapshot_history.get(shard_to_update).unwrap(); + assert_eq!(2, snapshot_history.len()); + assert_eq!(new_state, state_snapshot_repository.load_latest(shard_to_update).unwrap()); + assert_eq!(2, file_io.get_states_for_shard(shard_to_update).unwrap().len()); + } + + #[test] + fn update_latest_prunes_states_when_above_cache_size() { + let shard_id = ShardIdentifier::random(); + let (file_io, mut state_snapshot_repository) = + create_state_snapshot_repository(&[shard_id], TEST_SNAPSHOT_REPOSITORY_CACHE_SIZE); + + let states: Vec = + [1u64, 2u64, 3u64, 4u64, 5u64, 6u64].into_iter().map(|i| TestState(i)).collect(); + assert!(states.len() > TEST_SNAPSHOT_REPOSITORY_CACHE_SIZE); // ensures we have pruning + + states.iter().for_each(|state| { + let _ = state_snapshot_repository.update(&shard_id, state, Default::default()).unwrap(); + }); + + let snapshot_history = state_snapshot_repository.snapshot_history.get(&shard_id).unwrap(); + assert_eq!(TEST_SNAPSHOT_REPOSITORY_CACHE_SIZE, snapshot_history.len()); + assert_eq!( + *states.last().unwrap(), + state_snapshot_repository.load_latest(&shard_id).unwrap() + ); + assert_eq!( + TEST_SNAPSHOT_REPOSITORY_CACHE_SIZE, + file_io.get_states_for_shard(&shard_id).unwrap().len() + ); + } + + #[test] + fn update_latest_with_new_shard_creates_entry_and_does_not_modify_original_shard_entry() { + let shard_id = ShardIdentifier::random(); + let (file_io, mut state_snapshot_repository) = + create_state_snapshot_repository(&[shard_id], TEST_SNAPSHOT_REPOSITORY_CACHE_SIZE); + + assert!(state_snapshot_repository + .update(&ShardIdentifier::from_low_u64_be(1u64), &TestState(45), Default::default()) + .is_ok()); + + assert_eq!(2, state_snapshot_repository.snapshot_history.len()); + let snapshot_history = state_snapshot_repository.snapshot_history.get(&shard_id).unwrap(); + assert_eq!(1, snapshot_history.len()); + assert_eq!(TestState(0u64), state_snapshot_repository.load_latest(&shard_id).unwrap()); + assert_eq!(1, file_io.get_states_for_shard(&shard_id).unwrap().len()); + } + + #[test] + fn revert_to_removes_version_newer_than_target_hash() { + let shard_id = ShardIdentifier::random(); + let (file_io, mut state_snapshot_repository) = + create_state_snapshot_repository(&[shard_id], 6); + + let states: Vec = + [1u64, 2u64, 3u64, 4u64, 5u64].into_iter().map(|i| TestState(i)).collect(); + + let state_hashes = states + .iter() + .map(|state| { + let state_hash = state.hash(); + state_snapshot_repository.update(&shard_id, state, state_hash).unwrap(); + state_hash + }) + .collect::>(); + let revert_target_hash = state_hashes.get(1).unwrap(); + + let reverted_state = + state_snapshot_repository.revert_to(&shard_id, revert_target_hash).unwrap(); + + assert_eq!(TestState(2u64), reverted_state); + assert_eq!(3, state_snapshot_repository.snapshot_history.get(&shard_id).unwrap().len()); // because we have initialized version '0' as well + assert_eq!(TestState(2u64), state_snapshot_repository.load_latest(&shard_id).unwrap()); + assert_eq!(3, file_io.get_states_for_shard(&shard_id).unwrap().len()); + } + + #[test] + fn initializing_new_shard_works() { + let (_, mut state_snapshot_repository) = create_state_snapshot_repository(&[], 2); + + let shard_id = ShardIdentifier::random(); + + assert!(state_snapshot_repository.load_latest(&shard_id).is_err()); + assert!(state_snapshot_repository.list_shards().unwrap().is_empty()); + + let _hash = state_snapshot_repository + .initialize_new_shard(shard_id, &Default::default()) + .unwrap(); + + assert!(state_snapshot_repository.load_latest(&shard_id).is_ok()); + assert_eq!(1, state_snapshot_repository.list_shards().unwrap().len()); + } + + #[test] + fn initialize_new_state_when_shard_already_exists_returns_ok() { + let shard_id = ShardIdentifier::random(); + let (_, mut state_snapshot_repository) = create_state_snapshot_repository(&[shard_id], 2); + + let _hash = state_snapshot_repository + .initialize_new_shard(shard_id, &Default::default()) + .unwrap(); + + assert!(state_snapshot_repository.load_latest(&shard_id).is_ok()); + assert_eq!(1, state_snapshot_repository.list_shards().unwrap().len()); + } + + fn create_state_snapshot_repository( + shards: &[ShardIdentifier], + snapshot_history_size: usize, + ) -> (Arc, TestSnapshotRepository) { + let file_io = create_test_file_io(shards); + let state_initializer = Arc::new(TestStateInitializer::new(Default::default())); + let repository_loader = + StateSnapshotRepositoryLoader::new(file_io.clone(), state_initializer); + (file_io, repository_loader.load_snapshot_repository(snapshot_history_size).unwrap()) + } + + fn create_test_file_io(shards: &[ShardIdentifier]) -> Arc { + Arc::new(TestFileIo::new(shards, Box::new(|x| *x), Box::new(|x| x))) + } +} diff --git a/tee-worker/core-primitives/stf-state-handler/src/state_snapshot_repository_loader.rs b/tee-worker/core-primitives/stf-state-handler/src/state_snapshot_repository_loader.rs new file mode 100644 index 0000000000..88682efd74 --- /dev/null +++ b/tee-worker/core-primitives/stf-state-handler/src/state_snapshot_repository_loader.rs @@ -0,0 +1,221 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +use crate::{ + error::Result, + file_io::StateFileIo, + state_initializer::InitializeState, + state_snapshot_primitives::{ + initialize_shard_with_snapshot, SnapshotHistory, StateId, StateSnapshotMetaData, + }, + state_snapshot_repository::StateSnapshotRepository, +}; +use itp_hashing::Hash; +use itp_types::ShardIdentifier; +use log::*; +use std::{collections::VecDeque, fmt::Debug, iter::FromIterator, sync::Arc, vec::Vec}; + +/// Loads a state snapshot repository from existing shards directory with state files. +pub struct StateSnapshotRepositoryLoader { + file_io: Arc, + state_initializer: Arc, +} + +impl StateSnapshotRepositoryLoader +where + FileIo: StateFileIo, + ::HashType: Copy + Eq + Debug, + ::StateType: Clone + Hash, + StateInitializer: InitializeState, +{ + pub fn new(file_io: Arc, state_initializer: Arc) -> Self { + Self { file_io, state_initializer } + } + + /// Load a state snapshot repository from an existing set of files and directories. + pub fn load_snapshot_repository( + &self, + snapshot_history_cache_size: usize, + ) -> Result> { + let snapshot_history = self.load_and_initialize_state_snapshot_history()?; + + StateSnapshotRepository::new( + self.file_io.clone(), + snapshot_history_cache_size, + snapshot_history, + ) + } + + fn load_and_initialize_state_snapshot_history( + &self, + ) -> Result> { + let mut repository = SnapshotHistory::new(); + + let shards = self.file_io.list_shards()?; + debug!("Found {} shard(s) to load state from", shards.len()); + + for shard in shards { + let mut state_ids = self.file_io.list_state_ids_for_shard(&shard)?; + // Sort by id (which are timestamp), highest, i.e. newest, first + state_ids.sort_unstable(); + state_ids.reverse(); + + let mut snapshot_metadata: Vec<_> = self.map_to_snapshot_metadata(&shard, state_ids); + + if snapshot_metadata.is_empty() { + warn!( + "No (valid) states found for shard {:?}, initializing empty shard state", + shard + ); + let initial_state = self.state_initializer.initialize()?; + let initial_snapshot_metadata = + initialize_shard_with_snapshot(&shard, self.file_io.as_ref(), &initial_state)?; + snapshot_metadata.push(initial_snapshot_metadata); + } else { + debug!( + "Found {} state snapshot(s) for shard {}, latest snapshot is {}", + snapshot_metadata.len(), + &shard, + snapshot_metadata.first().map(|f| f.state_id).unwrap_or_default() + ); + } + + let snapshot_history = VecDeque::from_iter(snapshot_metadata); + + repository.insert(shard, snapshot_history); + } + Ok(repository) + } + + fn map_to_snapshot_metadata( + &self, + shard: &ShardIdentifier, + state_ids: Vec, + ) -> Vec> { + state_ids + .into_iter() + .flat_map(|state_id| match self.file_io.compute_hash(shard, state_id) { + Ok(hash) => Some(StateSnapshotMetaData::new(hash, state_id)), + Err(e) => { + warn!( + "Failed to compute hash for state snapshot with id {}: {:?}, ignoring snapshot as a result", + state_id, e + ); + None + }, + }) + .collect() + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::{ + in_memory_state_file_io::InMemoryStateFileIo, + test::mocks::initialize_state_mock::InitializeStateMock, + }; + use codec::Encode; + use itp_types::H256; + use sp_core::blake2_256; + + #[derive(Encode, Clone, Default, Copy)] + struct TestState(pub u64); + + type TestStateHash = H256; + type TestFileIo = InMemoryStateFileIo; + type TestStateInitializer = InitializeStateMock; + type TestLoader = StateSnapshotRepositoryLoader; + + impl Hash for TestState { + fn hash(&self) -> TestStateHash { + blake2_256(&self.encode()).into() + } + } + + #[test] + fn loading_from_empty_shard_directories_initializes_files() { + let shards = + vec![ShardIdentifier::random(), ShardIdentifier::random(), ShardIdentifier::random()]; + let (_, loader) = create_test_fixtures(shards.as_slice()); + + let snapshot_history = loader.load_and_initialize_state_snapshot_history().unwrap(); + assert_eq!(shards.len(), snapshot_history.len()); + for snapshots in snapshot_history.values() { + assert_eq!(1, snapshots.len()); + } + } + + #[test] + fn loading_without_shards_returns_empty_directory() { + let (_, loader) = create_test_fixtures(&[]); + + let snapshot_history = loader.load_and_initialize_state_snapshot_history().unwrap(); + assert!(snapshot_history.is_empty()); + } + + #[test] + fn loading_from_files_orders_by_timestamp() { + let shards = + vec![ShardIdentifier::random(), ShardIdentifier::random(), ShardIdentifier::random()]; + let (file_io, loader) = create_test_fixtures(shards.as_slice()); + + add_state_snapshots( + file_io.as_ref(), + &shards[0], + &[1_000_000, 2_000_000, 3_000_000, 4_000_000], + ); + add_state_snapshots(file_io.as_ref(), &shards[1], &[10_000_000, 9_000_000]); + add_state_snapshots(file_io.as_ref(), &shards[2], &[14_000_000, 11_000_000, 12_000_000]); + + let snapshot_history = loader.load_and_initialize_state_snapshot_history().unwrap(); + + assert_eq!(shards.len(), snapshot_history.len()); + assert_latest_state_id(&snapshot_history, &shards[0], 4_000_000); + assert_latest_state_id(&snapshot_history, &shards[1], 10_000_000); + assert_latest_state_id(&snapshot_history, &shards[2], 14_000_000); + } + + fn add_state_snapshots(file_io: &TestFileIo, shard: &ShardIdentifier, state_ids: &[StateId]) { + for state_id in state_ids { + add_snapshot_with_state_ids(file_io, shard, *state_id); + } + } + + fn add_snapshot_with_state_ids( + file_io: &TestFileIo, + shard: &ShardIdentifier, + state_id: StateId, + ) { + file_io.initialize_shard(shard, state_id, &Default::default()).unwrap(); + } + + fn assert_latest_state_id( + snapshot_history: &SnapshotHistory, + shard: &ShardIdentifier, + state_id: StateId, + ) { + assert_eq!(snapshot_history.get(shard).unwrap().front().unwrap().state_id, state_id) + } + + fn create_test_fixtures(shards: &[ShardIdentifier]) -> (Arc, TestLoader) { + let file_io = Arc::new(TestFileIo::new(shards, Box::new(|x| *x), Box::new(|x| x))); + let state_initializer = Arc::new(TestStateInitializer::new(Default::default())); + let loader = StateSnapshotRepositoryLoader::new(file_io.clone(), state_initializer); + (file_io, loader) + } +} diff --git a/tee-worker/core-primitives/stf-state-handler/src/test/mocks/initialize_state_mock.rs b/tee-worker/core-primitives/stf-state-handler/src/test/mocks/initialize_state_mock.rs new file mode 100644 index 0000000000..32ed41e671 --- /dev/null +++ b/tee-worker/core-primitives/stf-state-handler/src/test/mocks/initialize_state_mock.rs @@ -0,0 +1,42 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +use crate::{error::Result, state_initializer::InitializeState}; +use std::marker::PhantomData; + +/// Initialize state mock. +pub struct InitializeStateMock { + init_state: State, + _phantom: PhantomData, +} + +impl InitializeStateMock { + pub fn new(init_state: State) -> Self { + Self { init_state, _phantom: Default::default() } + } +} + +impl InitializeState for InitializeStateMock +where + State: Clone, +{ + type StateType = State; + + fn initialize(&self) -> Result { + Ok(self.init_state.clone()) + } +} diff --git a/tee-worker/core-primitives/stf-state-handler/src/test/mocks/mod.rs b/tee-worker/core-primitives/stf-state-handler/src/test/mocks/mod.rs new file mode 100644 index 0000000000..4a6fcfae26 --- /dev/null +++ b/tee-worker/core-primitives/stf-state-handler/src/test/mocks/mod.rs @@ -0,0 +1,20 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +pub mod initialize_state_mock; +pub mod state_key_repository_mock; +pub mod versioned_state_access_mock; diff --git a/tee-worker/core-primitives/stf-state-handler/src/test/mocks/state_key_repository_mock.rs b/tee-worker/core-primitives/stf-state-handler/src/test/mocks/state_key_repository_mock.rs new file mode 100644 index 0000000000..443877083d --- /dev/null +++ b/tee-worker/core-primitives/stf-state-handler/src/test/mocks/state_key_repository_mock.rs @@ -0,0 +1,68 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +#[cfg(feature = "sgx")] +use std::sync::SgxRwLock as RwLock; + +#[cfg(feature = "std")] +use std::sync::RwLock; + +use itp_sgx_crypto::{ + error::Result, + key_repository::{AccessKey, MutateKey}, + StateCrypto, +}; + +#[derive(Default)] +pub struct StateKeyRepositoryMock +where + KeyType: StateCrypto + Clone + Default, +{ + key: RwLock, +} + +impl StateKeyRepositoryMock +where + KeyType: StateCrypto + Clone + Default, +{ + #[cfg(all(feature = "test", feature = "sgx"))] + pub fn new(key: KeyType) -> Self { + StateKeyRepositoryMock { key: RwLock::new(key) } + } +} + +impl AccessKey for StateKeyRepositoryMock +where + KeyType: StateCrypto + Clone + Default, +{ + type KeyType = KeyType; + + fn retrieve_key(&self) -> Result { + Ok(self.key.read().unwrap().clone()) + } +} + +impl MutateKey for StateKeyRepositoryMock +where + KeyType: StateCrypto + Clone + Default, +{ + fn update_key(&self, key: KeyType) -> Result<()> { + let mut lock = self.key.write().unwrap(); + *lock = key; + Ok(()) + } +} diff --git a/tee-worker/core-primitives/stf-state-handler/src/test/mocks/versioned_state_access_mock.rs b/tee-worker/core-primitives/stf-state-handler/src/test/mocks/versioned_state_access_mock.rs new file mode 100644 index 0000000000..f6dee1730b --- /dev/null +++ b/tee-worker/core-primitives/stf-state-handler/src/test/mocks/versioned_state_access_mock.rs @@ -0,0 +1,102 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +use crate::{ + error::{Error, Result}, + state_snapshot_repository::VersionedStateAccess, +}; +use itp_types::ShardIdentifier; +use std::{ + collections::{HashMap, VecDeque}, + marker::PhantomData, + string::ToString, + vec::Vec, +}; + +#[derive(Default, Clone)] +pub struct VersionedStateAccessMock { + state_history: HashMap>, + phantom_data: PhantomData, +} + +impl VersionedStateAccessMock { + #[cfg(test)] + pub fn new(state_history: HashMap>) -> Self { + VersionedStateAccessMock { state_history, phantom_data: Default::default() } + } +} + +impl VersionedStateAccess for VersionedStateAccessMock +where + State: Default + Clone, + Hash: Default, +{ + type StateType = State; + type HashType = Hash; + + fn load_latest(&self, shard_identifier: &ShardIdentifier) -> Result { + self.state_history + .get(shard_identifier) + .ok_or(Error::InvalidShard(*shard_identifier))? + .front() + .cloned() + .ok_or(Error::StateNotFoundInRepository("".to_string())) + } + + fn update( + &mut self, + shard_identifier: &ShardIdentifier, + state: &Self::StateType, + _state_hash: Self::HashType, + ) -> Result<()> { + let state_history = self + .state_history + .entry(*shard_identifier) + .or_insert_with(|| VecDeque::default()); + state_history.push_front(state.clone()); + Ok(()) + } + + fn revert_to( + &mut self, + shard_identifier: &ShardIdentifier, + _state_hash: &Self::HashType, + ) -> Result { + let state_history = self + .state_history + .get_mut(shard_identifier) + .ok_or_else(|| Error::InvalidShard(*shard_identifier))?; + state_history.drain(..).last().ok_or(Error::EmptyRepository) + } + + fn initialize_new_shard( + &mut self, + shard_identifier: ShardIdentifier, + state: &Self::StateType, + ) -> Result { + self.state_history.insert(shard_identifier, VecDeque::from([state.clone()])); + Ok(Hash::default()) + } + + fn shard_exists(&self, shard_identifier: &ShardIdentifier) -> bool { + self.state_history.get(shard_identifier).is_some() + } + + fn list_shards(&self) -> Result> { + Ok(self.state_history.keys().copied().collect()) + } +} diff --git a/tee-worker/core-primitives/stf-state-handler/src/test/mod.rs b/tee-worker/core-primitives/stf-state-handler/src/test/mod.rs new file mode 100644 index 0000000000..e3552cd37f --- /dev/null +++ b/tee-worker/core-primitives/stf-state-handler/src/test/mod.rs @@ -0,0 +1,25 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +#[cfg(test)] +pub(crate) mod mocks; + +#[cfg(all(feature = "test", feature = "sgx"))] +pub mod mocks; + +#[cfg(all(feature = "test", feature = "sgx"))] +pub mod sgx_tests; diff --git a/tee-worker/core-primitives/stf-state-handler/src/test/sgx_tests.rs b/tee-worker/core-primitives/stf-state-handler/src/test/sgx_tests.rs new file mode 100644 index 0000000000..5fdf297b94 --- /dev/null +++ b/tee-worker/core-primitives/stf-state-handler/src/test/sgx_tests.rs @@ -0,0 +1,376 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +use crate::{ + error::{Error, Result}, + file_io::{ + purge_shard_dir, + sgx::{init_shard, shard_exists, SgxStateFileIo}, + shard_path, StateFileIo, + }, + handle_state::HandleState, + in_memory_state_file_io::sgx::create_in_memory_state_io_from_shards_directories, + query_shard_state::QueryShardState, + state_handler::StateHandler, + state_snapshot_repository::{StateSnapshotRepository, VersionedStateAccess}, + state_snapshot_repository_loader::StateSnapshotRepositoryLoader, + test::mocks::initialize_state_mock::InitializeStateMock, +}; +use codec::{Decode, Encode}; +use ita_stf::{State as StfState, StateType as StfStateType}; +use itp_sgx_crypto::{mocks::KeyRepositoryMock, Aes, AesSeal, StateCrypto}; +use itp_sgx_externalities::{SgxExternalities, SgxExternalitiesTrait}; +use itp_sgx_io::{write, StaticSealedIO}; +use itp_stf_state_observer::state_observer::StateObserver; +use itp_types::{ShardIdentifier, H256}; +use sp_core::hashing::blake2_256; +use std::{sync::Arc, thread, vec::Vec}; + +const STATE_SNAPSHOTS_CACHE_SIZE: usize = 3; + +type StateKeyRepositoryMock = KeyRepositoryMock; +type TestStateInitializer = InitializeStateMock; +type TestStateFileIo = SgxStateFileIo; +type TestStateRepository = StateSnapshotRepository; +type TestStateRepositoryLoader = + StateSnapshotRepositoryLoader; +type TestStateObserver = StateObserver; +type TestStateHandler = StateHandler; + +/// Directory handle to automatically initialize a directory +/// and upon dropping the reference, removing it again. +struct ShardDirectoryHandle { + shard: ShardIdentifier, +} + +impl ShardDirectoryHandle { + pub fn new(shard: ShardIdentifier) -> Result { + given_initialized_shard(&shard)?; + Ok(ShardDirectoryHandle { shard }) + } +} + +impl Drop for ShardDirectoryHandle { + fn drop(&mut self) { + purge_shard_dir(&self.shard) + } +} + +// Fixme: Move this test to sgx-runtime: +// +// https://github.com/integritee-network/sgx-runtime/issues/23 +pub fn test_sgx_state_decode_encode_works() { + // given + let state = given_hello_world_state(); + + // when + let encoded_state = state.state.encode(); + let state2 = StfStateType::decode(&mut encoded_state.as_slice()).unwrap(); + + // then + assert_eq!(state.state, state2); +} + +pub fn test_encrypt_decrypt_state_type_works() { + // given + let state = given_hello_world_state(); + let state_key = AesSeal::unseal_from_static_file().unwrap(); + + // when + let mut state_buffer = state.state.encode(); + state_key.encrypt(&mut state_buffer).unwrap(); + + state_key.decrypt(&mut state_buffer).unwrap(); + let decoded = StfStateType::decode(&mut state_buffer.as_slice()).unwrap(); + + // then + assert_eq!(state.state, decoded); +} + +pub fn test_write_and_load_state_works() { + // given + let shard: ShardIdentifier = [94u8; 32].into(); + let (state_handler, shard_dir_handle) = initialize_state_handler_with_directory_handle(&shard); + + let state = given_hello_world_state(); + + // when + let (lock, _s) = state_handler.load_for_mutation(&shard).unwrap(); + let _hash = state_handler.write_after_mutation(state.clone(), lock, &shard).unwrap(); + + let result = state_handler.load(&shard).unwrap(); + + // then + assert_eq!(state.state, result.state); + + // clean up + std::mem::drop(shard_dir_handle); +} + +pub fn test_ensure_subsequent_state_loads_have_same_hash() { + // given + let shard: ShardIdentifier = [49u8; 32].into(); + let (state_handler, shard_dir_handle) = initialize_state_handler_with_directory_handle(&shard); + + let (lock, initial_state) = state_handler.load_for_mutation(&shard).unwrap(); + state_handler.write_after_mutation(initial_state.clone(), lock, &shard).unwrap(); + + let state_loaded = state_handler.load(&shard).unwrap(); + + assert_eq!(hash_of(&initial_state.state), hash_of(&state_loaded.state)); + + // clean up + std::mem::drop(shard_dir_handle); +} + +fn hash_of(encodable: &T) -> H256 { + encodable.using_encoded(blake2_256).into() +} + +pub fn test_write_access_locks_read_until_finished() { + // here we want to test that a lock we obtain for + // mutating state locks out any read attempt that happens during that time + + // given + let shard: ShardIdentifier = [47u8; 32].into(); + let (state_handler, shard_dir_handle) = initialize_state_handler_with_directory_handle(&shard); + + let new_state_key = "my_new_state".encode(); + let (lock, mut state_to_mutate) = state_handler.load_for_mutation(&shard).unwrap(); + + // spawn a new thread that reads state + // this thread should be blocked until the write lock is released, i.e. until + // the new state is written. We can verify this, by trying to read that state variable + // that will be inserted further down below + let new_state_key_for_read = new_state_key.clone(); + let state_handler_clone = state_handler.clone(); + let shard_for_read = shard.clone(); + let join_handle = thread::spawn(move || { + let state_to_read = state_handler_clone.load(&shard_for_read).unwrap(); + assert!(state_to_read.get(new_state_key_for_read.as_slice()).is_some()); + }); + + assert!(state_to_mutate.get(new_state_key.clone().as_slice()).is_none()); + state_to_mutate.insert(new_state_key, "mega_secret_value".encode()); + + let _hash = state_handler.write_after_mutation(state_to_mutate, lock, &shard).unwrap(); + + join_handle.join().unwrap(); + + // clean up + std::mem::drop(shard_dir_handle); +} + +pub fn test_state_handler_file_backend_is_initialized() { + let shard: ShardIdentifier = [11u8; 32].into(); + let (state_handler, shard_dir_handle) = initialize_state_handler_with_directory_handle(&shard); + + assert!(state_handler.shard_exists(&shard).unwrap()); + assert!(1 <= state_handler.list_shards().unwrap().len()); // only greater equal, because there might be other (non-test) shards present + assert_eq!(1, number_of_files_in_shard_dir(&shard).unwrap()); // creates a first initialized file + + let _state = state_handler.load(&shard).unwrap(); + + assert_eq!(1, number_of_files_in_shard_dir(&shard).unwrap()); + + // clean up + std::mem::drop(shard_dir_handle); +} + +pub fn test_multiple_state_updates_create_snapshots_up_to_cache_size() { + let shard: ShardIdentifier = [17u8; 32].into(); + let (state_handler, _shard_dir_handle) = initialize_state_handler_with_directory_handle(&shard); + + assert_eq!(1, number_of_files_in_shard_dir(&shard).unwrap()); + + let hash_1 = update_state( + state_handler.as_ref(), + &shard, + ("my_key_1".encode(), "mega_secret_value".encode()), + ); + assert_eq!(2, number_of_files_in_shard_dir(&shard).unwrap()); + + let hash_2 = update_state( + state_handler.as_ref(), + &shard, + ("my_key_2".encode(), "mega_secret_value222".encode()), + ); + assert_eq!(3, number_of_files_in_shard_dir(&shard).unwrap()); + + let hash_3 = update_state( + state_handler.as_ref(), + &shard, + ("my_key_3".encode(), "mega_secret_value3".encode()), + ); + assert_eq!(3, number_of_files_in_shard_dir(&shard).unwrap()); + + let hash_4 = update_state( + state_handler.as_ref(), + &shard, + ("my_key_3".encode(), "mega_secret_valuenot3".encode()), + ); + assert_eq!(3, number_of_files_in_shard_dir(&shard).unwrap()); + + assert_ne!(hash_1, hash_2); + assert_ne!(hash_1, hash_3); + assert_ne!(hash_1, hash_4); + assert_ne!(hash_2, hash_3); + assert_ne!(hash_2, hash_4); + assert_ne!(hash_3, hash_4); + + assert_eq!(STATE_SNAPSHOTS_CACHE_SIZE, number_of_files_in_shard_dir(&shard).unwrap()); +} + +pub fn test_file_io_get_state_hash_works() { + let shard: ShardIdentifier = [21u8; 32].into(); + let _shard_dir_handle = ShardDirectoryHandle::new(shard).unwrap(); + let state_key_access = + Arc::new(StateKeyRepositoryMock::new(AesSeal::unseal_from_static_file().unwrap())); + + let file_io = TestStateFileIo::new(state_key_access); + + let state_id = 1234u128; + let state_hash = file_io + .initialize_shard(&shard, state_id, &StfState::new(Default::default())) + .unwrap(); + assert_eq!(state_hash, file_io.compute_hash(&shard, state_id).unwrap()); + + let state_hash = file_io.write(&shard, state_id, &given_hello_world_state()).unwrap(); + assert_eq!(state_hash, file_io.compute_hash(&shard, state_id).unwrap()); +} + +pub fn test_state_files_from_handler_can_be_loaded_again() { + let shard: ShardIdentifier = [15u8; 32].into(); + let (state_handler, _shard_dir_handle) = initialize_state_handler_with_directory_handle(&shard); + + update_state(state_handler.as_ref(), &shard, ("test_key_1".encode(), "value1".encode())); + update_state(state_handler.as_ref(), &shard, ("test_key_2".encode(), "value2".encode())); + update_state( + state_handler.as_ref(), + &shard, + ("test_key_2".encode(), "value2_updated".encode()), + ); + update_state(state_handler.as_ref(), &shard, ("test_key_3".encode(), "value3".encode())); + + // We initialize another state handler to load the state from the changes we just made. + let updated_state_handler = initialize_state_handler(); + + assert_eq!(STATE_SNAPSHOTS_CACHE_SIZE, number_of_files_in_shard_dir(&shard).unwrap()); + assert_eq!( + &"value3".encode(), + updated_state_handler + .load(&shard) + .unwrap() + .state() + .get("test_key_3".encode().as_slice()) + .unwrap() + ); +} + +pub fn test_list_state_ids_ignores_files_not_matching_the_pattern() { + let shard: ShardIdentifier = [21u8; 32].into(); + let _shard_dir_handle = ShardDirectoryHandle::new(shard).unwrap(); + let state_key_access = + Arc::new(StateKeyRepositoryMock::new(AesSeal::unseal_from_static_file().unwrap())); + + let file_io = TestStateFileIo::new(state_key_access); + + let mut invalid_state_file_path = shard_path(&shard); + invalid_state_file_path.push("invalid-state.bin"); + write(&[0, 1, 2, 3, 4, 5], invalid_state_file_path).unwrap(); + + file_io + .initialize_shard(&shard, 1234, &StfState::new(Default::default())) + .unwrap(); + + assert_eq!(1, file_io.list_state_ids_for_shard(&shard).unwrap().len()); +} + +pub fn test_in_memory_state_initializes_from_shard_directory() { + let shard: ShardIdentifier = [45u8; 32].into(); + let _shard_dir_handle = ShardDirectoryHandle::new(shard).unwrap(); + + let file_io = create_in_memory_state_io_from_shards_directories().unwrap(); + let state_initializer = Arc::new(TestStateInitializer::new(StfState::new(Default::default()))); + let state_repository_loader = + StateSnapshotRepositoryLoader::new(file_io.clone(), state_initializer); + let state_snapshot_repository = state_repository_loader + .load_snapshot_repository(STATE_SNAPSHOTS_CACHE_SIZE) + .unwrap(); + + assert_eq!(1, file_io.get_states_for_shard(&shard).unwrap().len()); + assert!(state_snapshot_repository.shard_exists(&shard)); +} + +fn initialize_state_handler_with_directory_handle( + shard: &ShardIdentifier, +) -> (Arc, ShardDirectoryHandle) { + let shard_dir_handle = ShardDirectoryHandle::new(*shard).unwrap(); + (initialize_state_handler(), shard_dir_handle) +} + +fn initialize_state_handler() -> Arc { + let state_key_access = + Arc::new(StateKeyRepositoryMock::new(AesSeal::unseal_from_static_file().unwrap())); + let file_io = Arc::new(TestStateFileIo::new(state_key_access)); + let state_initializer = Arc::new(TestStateInitializer::new(StfState::new(Default::default()))); + let state_repository_loader = + TestStateRepositoryLoader::new(file_io, state_initializer.clone()); + let state_observer = Arc::new(TestStateObserver::default()); + let state_snapshot_repository = state_repository_loader + .load_snapshot_repository(STATE_SNAPSHOTS_CACHE_SIZE) + .unwrap(); + Arc::new( + TestStateHandler::load_from_repository( + state_snapshot_repository, + state_observer, + state_initializer, + ) + .unwrap(), + ) +} + +fn update_state( + state_handler: &TestStateHandler, + shard: &ShardIdentifier, + kv_pair: (Vec, Vec), +) -> H256 { + let (lock, mut state_to_mutate) = state_handler.load_for_mutation(shard).unwrap(); + state_to_mutate.insert(kv_pair.0, kv_pair.1); + state_handler.write_after_mutation(state_to_mutate, lock, shard).unwrap() +} + +fn given_hello_world_state() -> StfState { + let key: Vec = "hello".encode(); + let value: Vec = "world".encode(); + let mut state = StfState::new(Default::default()); + state.insert(key, value); + state +} + +fn given_initialized_shard(shard: &ShardIdentifier) -> Result<()> { + if shard_exists(&shard) { + purge_shard_dir(shard); + } + init_shard(&shard) +} + +fn number_of_files_in_shard_dir(shard: &ShardIdentifier) -> Result { + let shard_dir_path = shard_path(shard); + let files_in_dir = std::fs::read_dir(shard_dir_path).map_err(|e| Error::Other(e.into()))?; + Ok(files_in_dir.count()) +} diff --git a/tee-worker/core-primitives/stf-state-observer/Cargo.toml b/tee-worker/core-primitives/stf-state-observer/Cargo.toml new file mode 100644 index 0000000000..e40788c1ce --- /dev/null +++ b/tee-worker/core-primitives/stf-state-observer/Cargo.toml @@ -0,0 +1,35 @@ +[package] +authors = ["Integritee AG "] +edition = "2021" +name = "itp-stf-state-observer" +version = "0.9.0" + +[dependencies] +sgx_tstd = { branch = "master", git = "https://github.com/apache/teaclave-sgx-sdk.git", optional = true } + +# local deps +itp-types = { default-features = false, path = "../types" } + +# sgx enabled external libraries +thiserror_sgx = { package = "thiserror", git = "https://github.com/mesalock-linux/thiserror-sgx", tag = "sgx_1.1.3", optional = true } + +# std compatible external libraries (make sure these versions match with the sgx-enabled ones above) +thiserror = { version = "1.0", optional = true } + +# no-std dependencies +codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } +log = { version = "0.4", default-features = false } + +[features] +default = ["std"] +mocks = [] +sgx = [ + "sgx_tstd", + "thiserror_sgx", +] +std = [ + "codec/std", + "itp-types/std", + "log/std", + "thiserror", +] diff --git a/tee-worker/core-primitives/stf-state-observer/src/error.rs b/tee-worker/core-primitives/stf-state-observer/src/error.rs new file mode 100644 index 0000000000..914552fb86 --- /dev/null +++ b/tee-worker/core-primitives/stf-state-observer/src/error.rs @@ -0,0 +1,34 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +#[cfg(all(not(feature = "std"), feature = "sgx"))] +use crate::sgx_reexport_prelude::*; + +pub type Result = core::result::Result; + +use std::boxed::Box; + +/// State Observer Error. +#[derive(Debug, thiserror::Error)] +pub enum Error { + #[error("Current state is empty (not set)")] + CurrentStateEmpty, + #[error("Could not acquire lock, lock is poisoned")] + LockPoisoning, + #[error(transparent)] + Other(#[from] Box), +} diff --git a/tee-worker/core-primitives/stf-state-observer/src/lib.rs b/tee-worker/core-primitives/stf-state-observer/src/lib.rs new file mode 100644 index 0000000000..5da2bbbed9 --- /dev/null +++ b/tee-worker/core-primitives/stf-state-observer/src/lib.rs @@ -0,0 +1,38 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +#![cfg_attr(not(feature = "std"), no_std)] +#![cfg_attr(test, feature(assert_matches))] + +#[cfg(all(feature = "std", feature = "sgx"))] +compile_error!("feature \"std\" and feature \"sgx\" cannot be enabled at the same time"); + +#[cfg(all(not(feature = "std"), feature = "sgx"))] +extern crate sgx_tstd as std; + +// Re-export module to properly feature gate sgx and regular std environment. +#[cfg(all(not(feature = "std"), feature = "sgx"))] +pub mod sgx_reexport_prelude { + pub use thiserror_sgx as thiserror; +} + +pub mod error; +pub mod state_observer; +pub mod traits; + +#[cfg(feature = "mocks")] +pub mod mock; diff --git a/tee-worker/core-primitives/stf-state-observer/src/mock.rs b/tee-worker/core-primitives/stf-state-observer/src/mock.rs new file mode 100644 index 0000000000..335adf7b91 --- /dev/null +++ b/tee-worker/core-primitives/stf-state-observer/src/mock.rs @@ -0,0 +1,79 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +#[cfg(feature = "sgx")] +use std::sync::SgxRwLock as RwLock; + +#[cfg(feature = "std")] +use std::sync::RwLock; + +use crate::{ + error::{Error, Result}, + traits::{ObserveState, UpdateState}, +}; +use core::fmt::Debug; +use itp_types::ShardIdentifier; +use log::*; +use std::vec::Vec; + +/// Observe state mock. +#[derive(Default)] +pub struct ObserveStateMock { + state: RwLock>, +} + +impl ObserveStateMock { + pub fn new(state: StateType) -> Self { + Self { state: RwLock::new(Some(state)) } + } +} + +impl ObserveState for ObserveStateMock +where + StateType: Debug, +{ + type StateType = StateType; + + fn observe_state(&self, _shard: &ShardIdentifier, observation_func: F) -> Result + where + F: FnOnce(&mut Self::StateType) -> R, + { + let mut maybe_state_lock = self.state.write().unwrap(); + + match &mut *maybe_state_lock { + Some(state) => { + debug!("State value: {:?}", state); + Ok(observation_func(state)) + }, + None => Err(Error::CurrentStateEmpty), + } + } +} + +/// Update state mock. +#[derive(Default)] +pub struct UpdateStateMock { + pub queued_updates: RwLock>, +} + +impl UpdateState for UpdateStateMock { + fn queue_state_update(&self, shard: ShardIdentifier, state: StateType) -> Result<()> { + let mut updates_lock = self.queued_updates.write().unwrap(); + updates_lock.push((shard, state)); + Ok(()) + } +} diff --git a/tee-worker/core-primitives/stf-state-observer/src/state_observer.rs b/tee-worker/core-primitives/stf-state-observer/src/state_observer.rs new file mode 100644 index 0000000000..21c8042ac0 --- /dev/null +++ b/tee-worker/core-primitives/stf-state-observer/src/state_observer.rs @@ -0,0 +1,148 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +#[cfg(feature = "sgx")] +use std::sync::SgxRwLock as RwLock; + +#[cfg(feature = "std")] +use std::sync::RwLock; + +use crate::{ + error::{Error, Result}, + traits::{ObserveState, UpdateState}, +}; +use itp_types::ShardIdentifier; +use std::{collections::HashMap, vec::Vec}; + +/// State observer implementation. Receives updates in a dedicated queue. +/// These updates are applied every time an observation function is executed. +/// +#[derive(Default)] +pub struct StateObserver { + queued_state_updates: RwLock>, + current_state: RwLock>, +} + +impl StateObserver { + pub fn new(shard: ShardIdentifier, state: StateType) -> Self { + Self { + queued_state_updates: Default::default(), + current_state: RwLock::new(HashMap::from([(shard, state)])), + } + } + + pub fn from_map(states_map: HashMap) -> Self { + Self { queued_state_updates: Default::default(), current_state: RwLock::new(states_map) } + } + + fn apply_pending_update(&self) -> Result<()> { + let mut update_queue_lock = + self.queued_state_updates.write().map_err(|_| Error::LockPoisoning)?; + + let state_updates: Vec<_> = update_queue_lock.drain().collect(); + drop(update_queue_lock); + + if !state_updates.is_empty() { + let mut current_state_lock = + self.current_state.write().map_err(|_| Error::LockPoisoning)?; + for state_update in state_updates.into_iter() { + current_state_lock.insert(state_update.0, state_update.1); + } + drop(current_state_lock); + } + + Ok(()) + } +} + +impl ObserveState for StateObserver { + type StateType = StateType; + + fn observe_state(&self, shard: &ShardIdentifier, observation_func: F) -> Result + where + F: FnOnce(&mut Self::StateType) -> R, + { + // Check if there is a pending update and apply it. + self.apply_pending_update()?; + + // Execute the observation function. + let mut current_state_map_lock = + self.current_state.write().map_err(|_| Error::LockPoisoning)?; + + match current_state_map_lock.get_mut(shard) { + Some(s) => Ok(observation_func(s)), + None => Err(Error::CurrentStateEmpty), + } + } +} + +impl UpdateState for StateObserver { + fn queue_state_update(&self, shard: ShardIdentifier, state: StateType) -> Result<()> { + let mut update_queue_lock = + self.queued_state_updates.write().map_err(|_| Error::LockPoisoning)?; + update_queue_lock.insert(shard, state); + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use core::assert_matches::assert_matches; + + type TestState = u64; + + #[test] + fn default_constructs_empty_state() { + let state_observer = StateObserver::::default(); + + assert_matches!( + state_observer.observe_state(&shard(), |_| { () }), + Err(Error::CurrentStateEmpty) + ); + } + + #[test] + fn initializing_state_with_some_works() { + let state_observer = StateObserver::::new(shard(), 31u64); + assert_eq!(state_observer.observe_state(&shard(), |s| *s).unwrap(), 31u64); + } + + #[test] + fn observing_multiple_times_after_update_works() { + let state_observer = StateObserver::::default(); + + state_observer.queue_state_update(shard(), 42u64).unwrap(); + + assert_eq!(state_observer.observe_state(&shard(), |s| *s).unwrap(), 42u64); + assert_eq!(state_observer.observe_state(&shard(), |s| *s).unwrap(), 42u64); + assert_eq!(state_observer.observe_state(&shard(), |s| *s).unwrap(), 42u64); + } + + #[test] + fn updating_multiple_times_before_observation_just_keeps_last_value() { + let state_observer = StateObserver::::new(shard(), 31); + state_observer.queue_state_update(shard(), 42u64).unwrap(); + state_observer.queue_state_update(shard(), 57u64).unwrap(); + assert_eq!(1, state_observer.queued_state_updates.read().unwrap().len()); + assert_eq!(state_observer.observe_state(&shard(), |s| *s).unwrap(), 57u64); + } + + fn shard() -> ShardIdentifier { + ShardIdentifier::default() + } +} diff --git a/tee-worker/core-primitives/stf-state-observer/src/traits.rs b/tee-worker/core-primitives/stf-state-observer/src/traits.rs new file mode 100644 index 0000000000..617e50dab4 --- /dev/null +++ b/tee-worker/core-primitives/stf-state-observer/src/traits.rs @@ -0,0 +1,37 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +use crate::error::Result; +use itp_types::ShardIdentifier; + +/// Observe state trait. +pub trait ObserveState { + type StateType; + + /// Requires a &mut StateType because the externalities are always executed with a mutable reference. + /// Underneath it all, the environmental!() macro only knows mutable access unfortunately. + /// And since the sp-io interface is fixed and relies on the global instance created by environmental!(), + /// it forces &mut access upon us here, even though read-only access would be enough. + fn observe_state(&self, shard: &ShardIdentifier, observation_func: F) -> Result + where + F: FnOnce(&mut Self::StateType) -> R; +} + +/// Trait to queue a state update for an observer. +pub trait UpdateState { + fn queue_state_update(&self, shard: ShardIdentifier, state: StateType) -> Result<()>; +} diff --git a/tee-worker/core-primitives/storage/Cargo.toml b/tee-worker/core-primitives/storage/Cargo.toml new file mode 100644 index 0000000000..82c07e8066 --- /dev/null +++ b/tee-worker/core-primitives/storage/Cargo.toml @@ -0,0 +1,49 @@ +[package] +authors = ["Integritee AG "] +edition = "2021" +name = "itp-storage" +version = "0.9.0" + +[dependencies] +codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["chain-error"] } +derive_more = { version = "0.99.5" } +frame-metadata = { version = "15.0.0", features = ["v14"], default-features = false } +hash-db = { version = "0.15.2", default-features = false } +thiserror = { version = "1.0.26", optional = true } + +# sgx deps +sgx_tstd = { branch = "master", git = "https://github.com/apache/teaclave-sgx-sdk.git", optional = true } +thiserror-sgx = { package = "thiserror", git = "https://github.com/mesalock-linux/thiserror-sgx", tag = "sgx_1.1.3", optional = true } + +# substrate deps +frame-support = { default-features = false, git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.29" } +sp-core = { default-features = false, features = ["full_crypto"], git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.29" } +sp-runtime = { default-features = false, git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.29" } +sp-std = { default-features = false, git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.29" } +sp-trie = { default-features = false, git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.29" } + +# integritee +itp-types = { default-features = false, path = "../types" } + +[dev-dependencies] +sp-state-machine = { git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.29" } + +[features] +default = ["std"] +sgx = [ + "sgx_tstd", + "thiserror-sgx", +] +std = [ + "codec/std", + "frame-metadata/std", + "frame-support/std", + "hash-db/std", + "itp-types/std", + "sp-core/std", + "sp-runtime/std", + "sp-std/std", + "sp-trie/std", + "thiserror", +] +test = [] diff --git a/tee-worker/core-primitives/storage/src/error.rs b/tee-worker/core-primitives/storage/src/error.rs new file mode 100644 index 0000000000..9b859bfb8f --- /dev/null +++ b/tee-worker/core-primitives/storage/src/error.rs @@ -0,0 +1,43 @@ +#[cfg(all(not(feature = "std"), feature = "sgx"))] +use thiserror_sgx as thiserror; + +// error with std::error::Error implemented for std and sgx +#[derive(Debug, PartialEq, Eq, thiserror::Error)] +#[cfg(any(feature = "std", feature = "sgx"))] +pub enum Error { + #[error("No storage proof supplied")] + NoProofSupplied, + #[error("Supplied storage value does not match the value from the proof")] + WrongValue, + #[error("Invalid storage proof: StorageRootMismatch")] + StorageRootMismatch, + #[error("Storage value unavailable")] + StorageValueUnavailable, + #[error(transparent)] + #[cfg(feature = "std")] + Codec(#[from] codec::Error), + + // as `codec::Error` does not implement `std::error::Error` in `no-std`, + // we can't use the `#[from]` attribute. + #[error("Codec: {0}")] + #[cfg(not(feature = "std"))] + Codec(codec::Error), +} + +// error for bare `no_std`, which does not implement `std::error::Error` + +#[cfg(all(not(feature = "std"), not(feature = "sgx")))] +use derive_more::{Display, From}; + +// Simple error enum for no_std without std::error::Error implemented +#[derive(Debug, Display, PartialEq, Eq, From)] +#[cfg(all(not(feature = "std"), not(feature = "sgx")))] +pub enum Error { + NoProofSupplied, + /// Supplied storage value does not match the value from the proof + WrongValue, + /// InvalidStorageProof, + StorageRootMismatch, + StorageValueUnavailable, + Codec(codec::Error), +} diff --git a/tee-worker/core-primitives/storage/src/keys.rs b/tee-worker/core-primitives/storage/src/keys.rs new file mode 100644 index 0000000000..43de4f667e --- /dev/null +++ b/tee-worker/core-primitives/storage/src/keys.rs @@ -0,0 +1,71 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +use codec::Encode; +use frame_metadata::v14::StorageHasher; +use sp_std::vec::Vec; + +pub fn storage_value_key(module_prefix: &str, storage_prefix: &str) -> Vec { + let mut bytes = sp_core::twox_128(module_prefix.as_bytes()).to_vec(); + bytes.extend(&sp_core::twox_128(storage_prefix.as_bytes())[..]); + bytes +} + +pub fn storage_map_key( + module_prefix: &str, + storage_prefix: &str, + mapkey1: &K, + hasher1: &StorageHasher, +) -> Vec { + let mut bytes = sp_core::twox_128(module_prefix.as_bytes()).to_vec(); + bytes.extend(&sp_core::twox_128(storage_prefix.as_bytes())[..]); + bytes.extend(key_hash(mapkey1, hasher1)); + bytes +} + +pub fn storage_double_map_key( + module_prefix: &str, + storage_prefix: &str, + mapkey1: &K, + hasher1: &StorageHasher, + mapkey2: &Q, + hasher2: &StorageHasher, +) -> Vec { + let mut bytes = sp_core::twox_128(module_prefix.as_bytes()).to_vec(); + bytes.extend(&sp_core::twox_128(storage_prefix.as_bytes())[..]); + bytes.extend(key_hash(mapkey1, hasher1)); + bytes.extend(key_hash(mapkey2, hasher2)); + bytes +} + +/// generates the key's hash depending on the StorageHasher selected +fn key_hash(key: &K, hasher: &StorageHasher) -> Vec { + let encoded_key = key.encode(); + match hasher { + StorageHasher::Identity => encoded_key.to_vec(), + StorageHasher::Blake2_128 => sp_core::blake2_128(&encoded_key).to_vec(), + StorageHasher::Blake2_128Concat => { + // copied from substrate Blake2_128Concat::hash since StorageHasher is not public + let x: &[u8] = encoded_key.as_slice(); + sp_core::blake2_128(x).iter().chain(x.iter()).cloned().collect::>() + }, + StorageHasher::Blake2_256 => sp_core::blake2_256(&encoded_key).to_vec(), + StorageHasher::Twox128 => sp_core::twox_128(&encoded_key).to_vec(), + StorageHasher::Twox256 => sp_core::twox_256(&encoded_key).to_vec(), + StorageHasher::Twox64Concat => sp_core::twox_64(&encoded_key).to_vec(), + } +} diff --git a/tee-worker/core-primitives/storage/src/lib.rs b/tee-worker/core-primitives/storage/src/lib.rs new file mode 100644 index 0000000000..3a3b6f2a6d --- /dev/null +++ b/tee-worker/core-primitives/storage/src/lib.rs @@ -0,0 +1,35 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +#![cfg_attr(not(feature = "std"), no_std)] + +#[cfg(all(feature = "std", feature = "sgx"))] +compile_error!("feature \"std\" and feature \"sgx\" cannot be enabled at the same time"); + +#[cfg(all(not(feature = "std"), feature = "sgx"))] +extern crate sgx_tstd as std; + +pub use error::Error; +pub use frame_metadata::v14::StorageHasher; +pub use keys::*; +pub use proof::*; +pub use verify_storage_proof::*; + +pub mod error; +pub mod keys; +pub mod proof; +pub mod verify_storage_proof; diff --git a/tee-worker/core-primitives/storage/src/proof.rs b/tee-worker/core-primitives/storage/src/proof.rs new file mode 100644 index 0000000000..0d0cdd4ccf --- /dev/null +++ b/tee-worker/core-primitives/storage/src/proof.rs @@ -0,0 +1,119 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +//! Logic for checking Substrate storage proofs. + +use crate::error::Error; +use hash_db::{HashDB, Hasher, EMPTY_PREFIX}; +use sp_std::vec::Vec; +use sp_trie::{trie_types::TrieDB, MemoryDB, Trie, TrieDBBuilder}; + +pub type StorageProof = Vec>; + +/// This struct is used to read storage values from a subset of a Merklized database. The "proof" +/// is a subset of the nodes in the Merkle structure of the database, so that it provides +/// authentication against a known Merkle root as well as the values in the database themselves. +pub struct StorageProofChecker { + root: H::Out, + db: MemoryDB, +} + +impl StorageProofChecker { + /// Constructs a new storage proof checker. + /// + /// This returns an error if the given proof is invalid with respect to the given root. + pub fn new(root: H::Out, proof: StorageProof) -> Result { + let mut db = MemoryDB::default(); + for item in proof { + db.insert(EMPTY_PREFIX, &item); + } + let checker = StorageProofChecker { root, db }; + // Return error if trie would be invalid. + let _ = checker.trie()?; + Ok(checker) + } + + /// Reads a value from the available subset of storage. If the value cannot be read due to an + /// incomplete or otherwise invalid proof, this returns an error. + pub fn read_value(&self, key: &[u8]) -> Result>, Error> { + self.trie()? + .get(key) + .map(|value| value.map(|value| value.to_vec())) + .map_err(|_| Error::StorageValueUnavailable) + } + + fn trie(&self) -> Result, Error> { + if !self.db.contains(&self.root, EMPTY_PREFIX) { + Err(Error::StorageRootMismatch) + } else { + Ok(TrieDBBuilder::new(&self.db, &self.root).build()) + } + } + + pub fn check_proof( + root: H::Out, + storage_key: &[u8], + proof: StorageProof, + ) -> Result>, Error> { + let storage_checker = StorageProofChecker::::new(root, proof)?; + + storage_checker.read_value(storage_key) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + use sp_core::{Blake2Hasher, H256}; + use sp_state_machine::{backend::Backend, new_in_mem, prove_read}; + use sp_trie::HashKey; + + #[test] + fn storage_proof_check() { + // construct storage proof + let mut backend = new_in_mem::>(); + backend.insert( + vec![ + (None, vec![(b"key1".to_vec(), Some(b"value1".to_vec()))]), + (None, vec![(b"key2".to_vec(), Some(b"value2".to_vec()))]), + (None, vec![(b"key3".to_vec(), Some(b"value3".to_vec()))]), + // Value is too big to fit in a branch node + (None, vec![(b"key11".to_vec(), Some(vec![0u8; 32]))]), + ], + Default::default(), + ); + let root = backend.storage_root(std::iter::empty(), Default::default()).0; + let proof: StorageProof = prove_read(backend, &[&b"key1"[..], &b"key2"[..], &b"key22"[..]]) + .unwrap() + .iter_nodes() + .collect(); + + // check proof in runtime + let checker = >::new(root, proof.clone()).unwrap(); + assert_eq!(checker.read_value(b"key1"), Ok(Some(b"value1".to_vec()))); + assert_eq!(checker.read_value(b"key2"), Ok(Some(b"value2".to_vec()))); + assert_eq!(checker.read_value(b"key11111"), Err(Error::StorageValueUnavailable)); + assert_eq!(checker.read_value(b"key22"), Ok(None)); + + // checking proof against invalid commitment fails + assert_eq!( + >::new(H256::random(), proof).err(), + Some(Error::StorageRootMismatch) + ); + } +} diff --git a/tee-worker/core-primitives/storage/src/verify_storage_proof.rs b/tee-worker/core-primitives/storage/src/verify_storage_proof.rs new file mode 100644 index 0000000000..fab9fda455 --- /dev/null +++ b/tee-worker/core-primitives/storage/src/verify_storage_proof.rs @@ -0,0 +1,67 @@ +use crate::{error::Error, StorageProofChecker}; +use codec::Decode; +use frame_support::ensure; +use itp_types::storage::{StorageEntry, StorageEntryVerified}; +use sp_runtime::traits::Header as HeaderT; +use sp_std::prelude::Vec; + +pub trait VerifyStorageProof { + fn verify_storage_proof( + self, + header: &Header, + ) -> Result, Error>; +} + +impl VerifyStorageProof for StorageEntry> { + fn verify_storage_proof( + self, + header: &Header, + ) -> Result, Error> { + let proof = self.proof.as_ref().ok_or(Error::NoProofSupplied)?; + let actual = StorageProofChecker::<
::Hashing>::check_proof( + *header.state_root(), + &self.key, + proof.to_vec(), + )?; + + // Todo: Why do they do it like that, we could supply the proof only and get the value from the proof directly?? + ensure!(actual == self.value, Error::WrongValue); + + Ok(StorageEntryVerified { + key: self.key, + value: self + .value + .map(|v| Decode::decode(&mut v.as_slice())) + .transpose() + .map_err(Error::Codec)?, + }) + } +} + +/// Verify a set of storage entries +pub fn verify_storage_entries( + entries: impl IntoIterator, + header: &Header, +) -> Result>, Error> +where + S: Into>>, + Header: HeaderT, + V: Decode, +{ + let iter = into_storage_entry_iter(entries); + let mut verified_entries = Vec::with_capacity(iter.size_hint().0); + + for e in iter { + verified_entries.push(e.verify_storage_proof(header)?); + } + Ok(verified_entries) +} + +pub fn into_storage_entry_iter<'a, S>( + source: impl IntoIterator + 'a, +) -> impl Iterator>> + 'a +where + S: Into>>, +{ + source.into_iter().map(|s| s.into()) +} diff --git a/tee-worker/core-primitives/substrate-sgx/environmental/Cargo.toml b/tee-worker/core-primitives/substrate-sgx/environmental/Cargo.toml new file mode 100644 index 0000000000..892784dd30 --- /dev/null +++ b/tee-worker/core-primitives/substrate-sgx/environmental/Cargo.toml @@ -0,0 +1,15 @@ +[package] +authors = ["Parity Technologies "] +description = "Set scope-limited values can can be accessed statically" +edition = "2021" +license = "Apache-2.0" +name = "environmental" +version = "1.1.3" + +[dependencies] +sgx_tstd = { branch = "master", git = "https://github.com/apache/teaclave-sgx-sdk.git", optional = true, features = ["thread"] } + +[features] +default = ["std"] +sgx = ["sgx_tstd"] +std = [] diff --git a/tee-worker/core-primitives/substrate-sgx/environmental/src/lib.rs b/tee-worker/core-primitives/substrate-sgx/environmental/src/lib.rs new file mode 100644 index 0000000000..7671299615 --- /dev/null +++ b/tee-worker/core-primitives/substrate-sgx/environmental/src/lib.rs @@ -0,0 +1,479 @@ +// Copyright 2017-2020 Parity Technologies +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Safe global references to stack variables. +//! +//! Set up a global reference with environmental! macro giving it a name and type. +//! Use the `using` function scoped under its name to name a reference and call a function that +//! takes no parameters yet can access said reference through the similarly placed `with` function. +//! +//! # Examples +//! +//! ``` +//! #[macro_use] extern crate environmental; +//! // create a place for the global reference to exist. +//! environmental!(counter: u32); +//! fn stuff() { +//! // do some stuff, accessing the named reference as desired. +//! counter::with(|i| *i += 1); +//! } +//! fn main() { +//! // declare a stack variable of the same type as our global declaration. +//! let mut counter_value = 41u32; +//! // call stuff, setting up our `counter` environment as a reference to our counter_value var. +//! counter::using(&mut counter_value, stuff); +//! println!("The answer is {:?}", counter_value); // will print 42! +//! stuff(); // safe! doesn't do anything. +//! } +//! ``` +//! +//! Original crate: https://github.com/paritytech/environmental/blob/master/src/lib.rs +//! The original crate does not support multithreading in `no_std` mode, see https://github.com/integritee-network/worker/issues/803. +//! Therefore, this crate introduces the sgx feature, which allows multithreading within an sgx enabled environment. +//! It should be ensured that all uses of the environmental crate within the enclave are making use of this crate, not the original one. +//! +//! Attention: The `sp-runtime-interface` still points to the original environmental crate. It can't be easily patched due +//! to this crate not being `no_std` compatible. (See https://github.com/integritee-network/worker/pull/938#discussion_r952412587). +//! However, because `sp-runtime-interface` only uses environmental in `std` mode, it should be safe to leave as is. +//! Nonetheless, it should be kept in mind that this may cause a problem in the future. + +#![cfg_attr(not(feature = "std"), no_std)] + +#[cfg(all(feature = "std", feature = "sgx"))] +compile_error!("feature \"std\" and feature \"sgx\" cannot be enabled at the same time"); + +#[cfg(all(not(feature = "std"), not(feature = "sgx")))] +compile_error!("Either feature \"std\" or feature \"sgx\" must be enabled"); + +#[cfg(all(not(feature = "std"), feature = "sgx"))] +extern crate sgx_tstd as std; + +extern crate alloc; + +#[doc(hidden)] +pub use core::{ + cell::RefCell, + marker::PhantomData, + mem::{replace, transmute}, +}; + +#[doc(hidden)] +pub use alloc::{rc::Rc, vec::Vec}; + +#[doc(hidden)] +pub use std::thread::LocalKey; + +#[doc(hidden)] +#[macro_export] +macro_rules! thread_local_impl { + ($(#[$attr:meta])* static $name:ident: $t:ty = $init:expr) => ( + use std::thread_local; + thread_local!($(#[$attr])* static $name: $t = $init); + ); +} + +/// The global inner that stores the stack of globals. +#[doc(hidden)] +pub type GlobalInner = RefCell>>>; + +/// The global type. +type Global = LocalKey>; + +#[doc(hidden)] +pub fn using R>( + global: &'static Global, + protected: &mut T, + f: F, +) -> R { + // store the `protected` reference as a pointer so we can provide it to logic running within + // `f`. + // while we record this pointer (while it's non-zero) we guarantee: + // - it will only be used once at any time (no reentrancy); + // - that no other thread will use it; and + // - that we do not use the original mutating reference while the pointer. + // exists. + global.with(|r| { + // Push the new global to the end of the stack. + r.borrow_mut().push(Rc::new(RefCell::new(protected as _))); + + // Even if `f` panics the added global will be popped. + struct PopGlobal<'a, T: 'a + ?Sized> { + global_stack: &'a GlobalInner, + } + + impl<'a, T: 'a + ?Sized> Drop for PopGlobal<'a, T> { + fn drop(&mut self) { + self.global_stack.borrow_mut().pop(); + } + } + + let _guard = PopGlobal { global_stack: r }; + + f() + }) +} + +#[doc(hidden)] +pub fn with R>( + global: &'static Global, + mutator: F, +) -> Option { + global.with(|r| { + // We always use the `last` element when we want to access the + // currently set global. + let last = r.borrow().last().cloned(); + last.map(|ptr| + // safe because it's only non-zero when it's being called from using, which + // is holding on to the underlying reference (and not using it itself) safely. + unsafe { + mutator(&mut **ptr.borrow_mut()) + }) + }) +} + +/// Declare a new global reference module whose underlying value does not contain references. +/// +/// Will create a module of a given name that contains two functions: +/// +/// * `pub fn using R>(protected: &mut $t, f: F) -> R` +/// This executes `f`, returning its value. During the call, the module's reference is set to +/// be equal to `protected`. +/// * `pub fn with R>(f: F) -> Option` +/// This executes `f`, returning `Some` of its value if called from code that is being executed +/// as part of a `using` call. If not, it returns `None`. `f` is provided with one argument: the +/// same reference as provided to the most recent `using` call. +/// +/// # Examples +/// +/// Initializing the global context with a given value. +/// +/// ```rust +/// #[macro_use] extern crate environmental; +/// environmental!(counter: u32); +/// fn main() { +/// let mut counter_value = 41u32; +/// counter::using(&mut counter_value, || { +/// let odd = counter::with(|value| +/// if *value % 2 == 1 { +/// *value += 1; true +/// } else { +/// *value -= 3; false +/// }).unwrap(); // safe because we're inside a counter::using +/// println!("counter was {}", match odd { true => "odd", _ => "even" }); +/// }); +/// +/// println!("The answer is {:?}", counter_value); // 42 +/// } +/// ``` +/// +/// Roughly the same, but with a trait object: +/// +/// ```rust +/// #[macro_use] extern crate environmental; +/// +/// trait Increment { fn increment(&mut self); } +/// +/// impl Increment for i32 { +/// fn increment(&mut self) { *self += 1 } +/// } +/// +/// environmental!(val: dyn Increment + 'static); +/// +/// fn main() { +/// let mut local = 0i32; +/// val::using(&mut local, || { +/// val::with(|v| for _ in 0..5 { v.increment() }); +/// }); +/// +/// assert_eq!(local, 5); +/// } +/// ``` +#[macro_export] +macro_rules! environmental { + ($name:ident : $t:ty) => { + #[allow(non_camel_case_types)] + struct $name { __private_field: () } + + $crate::thread_local_impl! { + static GLOBAL: $crate::GlobalInner<$t> = Default::default() + } + + impl $name { + #[allow(unused_imports)] + + pub fn using R>( + protected: &mut $t, + f: F + ) -> R { + $crate::using(&GLOBAL, protected, f) + } + + pub fn with R>( + f: F + ) -> Option { + $crate::with(&GLOBAL, |x| f(x)) + } + } + }; + ($name:ident : trait @$t:ident [$($args:ty,)*]) => { + #[allow(non_camel_case_types, dead_code)] + struct $name { __private_field: () } + + $crate::thread_local_impl! { + static GLOBAL: $crate::GlobalInner<(dyn $t<$($args),*> + 'static)> + = Default::default() + } + + impl $name { + #[allow(unused_imports)] + + pub fn using R>( + protected: &mut dyn $t<$($args),*>, + f: F + ) -> R { + let lifetime_extended = unsafe { + $crate::transmute::<&mut dyn $t<$($args),*>, &mut (dyn $t<$($args),*> + 'static)>(protected) + }; + $crate::using(&GLOBAL, lifetime_extended, f) + } + + pub fn with FnOnce(&'a mut (dyn $t<$($args),*> + 'a)) -> R>( + f: F + ) -> Option { + $crate::with(&GLOBAL, |x| f(x)) + } + } + }; + ($name:ident<$traittype:ident> : trait $t:ident <$concretetype:ty>) => { + #[allow(non_camel_case_types, dead_code)] + struct $name { _private_field: $crate::PhantomData } + + $crate::thread_local_impl! { + static GLOBAL: $crate::GlobalInner<(dyn $t<$concretetype> + 'static)> + = Default::default() + } + + impl $name { + #[allow(unused_imports)] + pub fn using R>( + protected: &mut dyn $t, + f: F + ) -> R { + let lifetime_extended = unsafe { + $crate::transmute::<&mut dyn $t, &mut (dyn $t<$concretetype> + 'static)>(protected) + }; + $crate::using(&GLOBAL, lifetime_extended, f) + } + + pub fn with FnOnce(&'a mut (dyn $t<$concretetype> + 'a)) -> R>( + f: F + ) -> Option { + $crate::with(&GLOBAL, |x| f(x)) + } + } + }; + ($name:ident : trait $t:ident <>) => { $crate::environmental! { $name : trait @$t [] } }; + ($name:ident : trait $t:ident < $($args:ty),* $(,)* >) => { + $crate::environmental! { $name : trait @$t [$($args,)*] } + }; + ($name:ident : trait $t:ident) => { $crate::environmental! { $name : trait @$t [] } }; +} + +#[cfg(test)] +mod tests { + // Test trait in item position + #[allow(dead_code)] + mod trait_test { + trait Test {} + + environmental!(item_positon_trait: trait Test); + } + + // Test type in item position + #[allow(dead_code)] + mod type_test { + environmental!(item_position_type: u32); + } + + #[test] + fn simple_works() { + environmental!(counter: u32); + + fn stuff() { + counter::with(|value| *value += 1); + } + + // declare a stack variable of the same type as our global declaration. + let mut local = 41u32; + + // call stuff, setting up our `counter` environment as a reference to our local counter var. + counter::using(&mut local, stuff); + assert_eq!(local, 42); + stuff(); // safe! doesn't do anything. + assert_eq!(local, 42); + } + + #[test] + fn overwrite_with_lesser_lifetime() { + environmental!(items: Vec); + + let mut local_items = vec![1, 2, 3]; + items::using(&mut local_items, || { + let dies_at_end = vec![4, 5, 6]; + items::with(|items| *items = dies_at_end); + }); + + assert_eq!(local_items, vec![4, 5, 6]); + } + + #[test] + fn declare_with_trait_object() { + trait Foo { + fn get(&self) -> i32; + fn set(&mut self, x: i32); + } + + impl Foo for i32 { + fn get(&self) -> i32 { + *self + } + fn set(&mut self, x: i32) { + *self = x + } + } + + environmental!(foo: dyn Foo + 'static); + + fn stuff() { + foo::with(|value| { + let new_val = value.get() + 1; + value.set(new_val); + }); + } + + let mut local = 41i32; + foo::using(&mut local, stuff); + + assert_eq!(local, 42); + + stuff(); // doesn't do anything. + + assert_eq!(local, 42); + } + + #[test] + fn unwind_recursive() { + use std::panic; + + environmental!(items: Vec); + + let panicked = panic::catch_unwind(|| { + let mut local_outer = vec![1, 2, 3]; + + items::using(&mut local_outer, || { + let mut local_inner = vec![4, 5, 6]; + items::using(&mut local_inner, || { + panic!("are you unsafe?"); + }) + }); + }) + .is_err(); + + assert!(panicked); + + let mut was_cleared = true; + items::with(|_items| was_cleared = false); + + assert!(was_cleared); + } + + #[test] + fn use_non_static_trait() { + trait Sum { + fn sum(&self) -> usize; + } + impl Sum for &[usize] { + fn sum(&self) -> usize { + self.iter().fold(0, |a, c| a + c) + } + } + + environmental!(sum: trait Sum); + let numbers = vec![1, 2, 3, 4, 5]; + let mut numbers = &numbers[..]; + let got_sum = sum::using(&mut numbers, || sum::with(|x| x.sum())).unwrap(); + + assert_eq!(got_sum, 15); + } + + #[test] + fn stacking_globals() { + trait Sum { + fn sum(&self) -> usize; + } + impl Sum for &[usize] { + fn sum(&self) -> usize { + self.iter().fold(0, |a, c| a + c) + } + } + + environmental!(sum: trait Sum); + let numbers = vec![1, 2, 3, 4, 5]; + let mut numbers = &numbers[..]; + let got_sum = sum::using(&mut numbers, || { + sum::with(|_| { + let numbers2 = vec![1, 2, 3, 4, 5, 6]; + let mut numbers2 = &numbers2[..]; + sum::using(&mut numbers2, || sum::with(|x| x.sum())) + }) + }) + .unwrap() + .unwrap(); + + assert_eq!(got_sum, 21); + + assert!(sum::with(|_| ()).is_none()); + } + + #[test] + fn use_generic_trait() { + trait Plus { + fn plus42() -> usize; + } + struct ConcretePlus; + impl Plus for ConcretePlus { + fn plus42() -> usize { + 42 + } + } + trait Multiplier { + fn mul_and_add(&self) -> usize; + } + impl<'a, P: Plus> Multiplier

for &'a [usize] { + fn mul_and_add(&self) -> usize { + self.iter().fold(1, |a, c| a * c) + P::plus42() + } + } + + let numbers = vec![1, 2, 3]; + let mut numbers = &numbers[..]; + let out = foo::::using(&mut numbers, || { + foo::::with(|x| x.mul_and_add()) + }) + .unwrap(); + + assert_eq!(out, 6 + 42); + environmental!(foo: trait Multiplier); + } +} diff --git a/tee-worker/core-primitives/substrate-sgx/externalities/Cargo.toml b/tee-worker/core-primitives/substrate-sgx/externalities/Cargo.toml new file mode 100644 index 0000000000..0ab1cfa05f --- /dev/null +++ b/tee-worker/core-primitives/substrate-sgx/externalities/Cargo.toml @@ -0,0 +1,40 @@ +[package] +authors = ["Integritee AG and Parity Technologies "] +edition = "2021" +name = "itp-sgx-externalities" +version = "0.9.0" + +[dependencies] +# no_std +codec = { version = "3.0.0", package = "parity-scale-codec", default-features = false, features = ["derive", "chain-error"] } +derive_more = "0.99.16" +log = { version = "0.4", default-features = false } +postcard = { version = "0.7.2", default-features = false, features = ["alloc"] } +serde = { version = "1.0", default-features = false, features = ["derive", "alloc"] } + +# sgx dependencies +sgx_tstd = { optional = true, features = ["untrusted_fs", "net", "backtrace"], git = "https://github.com/apache/teaclave-sgx-sdk.git", branch = "master" } + +# substrate +sp-core = { default-features = false, git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.29" } + +# local +environmental = { default-features = false, path = "../environmental" } +itp-hashing = { default-features = false, path = "../../hashing" } + +[features] +default = ["std"] +sgx = [ + "sgx_tstd", + "environmental/sgx", +] +std = [ + "codec/std", + "environmental/std", + "itp-hashing/std", + "log/std", + "postcard/use-std", + "serde/std", + # substrate + "sp-core/std", +] diff --git a/tee-worker/core-primitives/substrate-sgx/externalities/src/bypass.rs b/tee-worker/core-primitives/substrate-sgx/externalities/src/bypass.rs new file mode 100644 index 0000000000..dcd5bd9f9c --- /dev/null +++ b/tee-worker/core-primitives/substrate-sgx/externalities/src/bypass.rs @@ -0,0 +1,60 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +//! Converts maps to vecs for serialization. +//! from https://github.com/DenisKolodin/vectorize +//! +//! `bypass` is necessary to force deriving serialization of complex type specs. + +use serde::{Deserialize, Deserializer, Serialize, Serializer}; + +#[allow(unused)] +pub fn serialize<'a, T, S>(target: T, ser: S) -> Result +where + S: Serializer, + T: Serialize + 'a, +{ + serde::Serialize::serialize(&target, ser) +} + +#[allow(unused)] +pub fn deserialize<'de, T, D>(des: D) -> Result +where + D: Deserializer<'de>, + T: Deserialize<'de>, +{ + serde::Deserialize::deserialize(des) +} + +#[cfg(test)] +mod tests { + use serde::{de::DeserializeOwned, Deserialize, Serialize}; + use std::fmt; + + trait Requirement: + DeserializeOwned + Serialize + Clone + fmt::Debug + Sync + Send + 'static + { + } + + trait ComplexSpec: Requirement {} + + #[derive(Debug, Serialize, Deserialize)] + struct MyComplexType { + #[serde(with = "super")] // = "vectorize::bypass" + inner: Option, + } +} diff --git a/tee-worker/core-primitives/substrate-sgx/externalities/src/codec_impl.rs b/tee-worker/core-primitives/substrate-sgx/externalities/src/codec_impl.rs new file mode 100644 index 0000000000..b65f9003f1 --- /dev/null +++ b/tee-worker/core-primitives/substrate-sgx/externalities/src/codec_impl.rs @@ -0,0 +1,149 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +//! Implement `parity-scale-codec` for the externalities. +//! +//! This is necessary workaround, as `Encode` and `Decode` can't directly be implemented on `HashMap` or `BTreeMap`. + +use codec::{Decode, Encode, Input}; +use serde::{de::DeserializeOwned, Serialize}; +use std::{vec, vec::Vec}; + +use crate::{SgxExternalitiesDiffType, SgxExternalitiesType}; + +impl Encode for SgxExternalitiesType { + fn encode(&self) -> Vec { + encode_with_serialize(&self) + } +} + +impl Decode for SgxExternalitiesType { + fn decode(input: &mut I) -> Result { + decode_with_deserialize(input) + } +} + +impl Encode for SgxExternalitiesDiffType { + fn encode(&self) -> Vec { + encode_with_serialize(&self) + } +} + +impl Decode for SgxExternalitiesDiffType { + fn decode(input: &mut I) -> Result { + decode_with_deserialize(input) + } +} + +fn encode_with_serialize(source: &T) -> Vec { + // We unwrap on purpose here in order to make sure we notice when something goes wrong. + // Before we returned an empty vec and logged the error. But this could go unnoticed in the + // caller and cause problems (in case the empty vec is also something valid) + postcard::to_allocvec(source).unwrap() +} + +fn decode_with_deserialize( + input: &mut I, +) -> Result { + let input_length = input + .remaining_len()? + .ok_or_else(|| codec::Error::from("Could not read length from input data"))?; + + let mut buff = vec![0u8; input_length]; + + input.read(&mut buff)?; + + postcard::from_bytes::<'_, T>(buff.as_slice()).map_err(|e| { + log::error!("deserialization failed: {:?}", e); + codec::Error::from("Could not decode with deserialize") + }) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::{InternalMap, SgxExternalities}; + use std::{ + collections::hash_map::DefaultHasher, + hash::{Hash, Hasher}, + }; + + #[test] + fn serializing_externalities_type_works() { + ensure_serialize_roundtrip_succeeds(create_default_state()); + } + + #[test] + fn serializing_externalities_diff_type_works() { + ensure_serialize_roundtrip_succeeds(create_default_state_diff()); + } + + #[test] + fn serializing_externalities_works() { + let externalities = SgxExternalities { + state: create_default_state(), + state_diff: create_default_state_diff(), + }; + + ensure_serialize_roundtrip_succeeds(externalities); + } + + #[test] + fn encoding_decoding_preserves_order() { + let externalities = create_default_state(); + let encoded_externalities = externalities.encode(); + let decoded_externalities: SgxExternalitiesType = + Decode::decode(&mut encoded_externalities.as_slice()).unwrap(); + let encoded_second_time_externalities = decoded_externalities.encode(); + + assert_eq!( + calculate_hash(&encoded_externalities), + calculate_hash(&encoded_second_time_externalities) + ); + } + + fn create_default_state_diff() -> SgxExternalitiesDiffType { + let mut map = InternalMap::>>::new(); + map.insert(Encode::encode("dings"), Some(Encode::encode("other"))); + map.insert(Encode::encode("item"), Some(Encode::encode("crate"))); + map.insert(Encode::encode("key"), None); + SgxExternalitiesDiffType(map) + } + + fn create_default_state() -> SgxExternalitiesType { + let mut map = InternalMap::>::new(); + map.insert(Encode::encode("dings"), Encode::encode("other")); + map.insert(Encode::encode("item"), Encode::encode("crate")); + SgxExternalitiesType(map) + } + + fn ensure_serialize_roundtrip_succeeds< + T: Serialize + DeserializeOwned + std::cmp::PartialEq + std::fmt::Debug, + >( + item: T, + ) { + let serialized_item = postcard::to_allocvec(&item).unwrap(); + let deserialized_item = postcard::from_bytes::<'_, T>(serialized_item.as_slice()).unwrap(); + assert_eq!(item, deserialized_item); + } + + fn calculate_hash(t: &T) -> u64 { + let mut s = DefaultHasher::new(); + t.hash(&mut s); + s.finish() + } +} diff --git a/tee-worker/core-primitives/substrate-sgx/externalities/src/lib.rs b/tee-worker/core-primitives/substrate-sgx/externalities/src/lib.rs new file mode 100644 index 0000000000..ec1cb6236a --- /dev/null +++ b/tee-worker/core-primitives/substrate-sgx/externalities/src/lib.rs @@ -0,0 +1,387 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ +#![cfg_attr(not(feature = "std"), no_std)] + +#[cfg(all(feature = "std", feature = "sgx"))] +compile_error!("feature \"std\" and feature \"sgx\" cannot be enabled at the same time"); + +#[cfg(feature = "sgx")] +extern crate sgx_tstd as std; + +use codec::{Decode, Encode, EncodeAppend}; +use core::ops::Bound; +use derive_more::{Deref, DerefMut, From, IntoIterator}; +use itp_hashing::Hash; +use serde::{Deserialize, Serialize}; +use sp_core::{hashing::blake2_256, H256}; +use std::{collections::BTreeMap, vec, vec::Vec}; + +pub use scope_limited::{set_and_run_with_externalities, with_externalities}; + +// Unfortunately we cannot use `serde_with::serde_as` to serialize our map (which would be very convenient) +// because it has pulls in the serde and serde_json dependency with `std`, not `default-features=no`. +// Instead we use https://github.com/DenisKolodin/vectorize which is very little code, copy-pasted +// directly into this code base. +//use serde_with::serde_as; + +mod codec_impl; +mod scope_limited; +// These are used to serialize a map with keys that are not string. +mod bypass; +mod vectorize; + +type InternalMap = BTreeMap, V>; + +#[derive(From, Deref, DerefMut, Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)] +pub struct SgxExternalitiesType(#[serde(with = "vectorize")] InternalMap>); + +#[derive( + From, + Deref, + DerefMut, + Clone, + Debug, + Default, + PartialEq, + Eq, + Serialize, + Deserialize, + IntoIterator, +)] +pub struct SgxExternalitiesDiffType(#[serde(with = "vectorize")] InternalMap>>); + +#[derive(Clone, Debug, Default, PartialEq, Eq, Encode, Decode, Serialize, Deserialize)] +pub struct SgxExternalities { + pub state: SgxExternalitiesType, + pub state_diff: SgxExternalitiesDiffType, +} + +pub trait StateHash { + fn hash(&self) -> H256; +} + +impl StateHash for SgxExternalities { + fn hash(&self) -> H256 { + self.state.using_encoded(blake2_256).into() + } +} + +impl Hash for SgxExternalities { + fn hash(&self) -> H256 { + ::hash(self) + } +} + +pub trait SgxExternalitiesTrait { + type SgxExternalitiesType; + type SgxExternalitiesDiffType; + + // Create new Externaltiies with empty diff. + fn new(state: Self::SgxExternalitiesType) -> Self; + + fn state(&self) -> &Self::SgxExternalitiesType; + + fn state_diff(&self) -> &Self::SgxExternalitiesDiffType; + + fn insert(&mut self, k: Vec, v: Vec) -> Option>; + + /// Append a value to an existing key. + fn append(&mut self, k: Vec, v: Vec); + + fn remove(&mut self, k: &[u8]) -> Option>; + + fn get(&self, k: &[u8]) -> Option<&Vec>; + + fn contains_key(&self, k: &[u8]) -> bool; + + /// Get the next key in state after the given one (excluded) in lexicographic order. + fn next_storage_key(&self, key: &[u8]) -> Option>; + + /// Clears all values that match the given key prefix. + fn clear_prefix(&mut self, key_prefix: &[u8], maybe_limit: Option) -> u32; + + /// Prunes the state diff. + fn prune_state_diff(&mut self); + + /// Execute the given closure while `self` is set as externalities. + /// + /// Returns the result of the given closure. + fn execute_with(&mut self, f: impl FnOnce() -> R) -> R; +} + +impl SgxExternalitiesTrait for SgxExternalities +where + SgxExternalitiesType: Encode + Decode, + SgxExternalitiesDiffType: Encode + Decode, +{ + type SgxExternalitiesType = SgxExternalitiesType; + type SgxExternalitiesDiffType = SgxExternalitiesDiffType; + + fn new(state: Self::SgxExternalitiesType) -> Self { + Self { state, state_diff: Default::default() } + } + + fn state(&self) -> &Self::SgxExternalitiesType { + &self.state + } + + fn state_diff(&self) -> &Self::SgxExternalitiesDiffType { + &self.state_diff + } + + fn insert(&mut self, key: Vec, value: Vec) -> Option> { + self.state_diff.insert(key.clone(), Some(value.clone())); + self.state.insert(key, value) + } + + fn append(&mut self, key: Vec, value: Vec) { + let current = self.state.entry(key.clone()).or_default(); + let updated_value = StorageAppend::new(current).append(value); + self.state_diff.insert(key, Some(updated_value)); + } + + fn remove(&mut self, key: &[u8]) -> Option> { + self.state_diff.insert(key.to_vec(), None); + self.state.remove(key) + } + + fn get(&self, key: &[u8]) -> Option<&Vec> { + self.state.get(key) + } + + fn contains_key(&self, key: &[u8]) -> bool { + self.state.contains_key(key) + } + + fn next_storage_key(&self, key: &[u8]) -> Option> { + let range = (Bound::Excluded(key), Bound::Unbounded); + self.state.range::<[u8], _>(range).next().map(|(k, _v)| k.to_vec()) // directly return k as _v is never None in our case + } + + fn prune_state_diff(&mut self) { + self.state_diff.clear(); + } + + fn clear_prefix(&mut self, key_prefix: &[u8], _maybe_limit: Option) -> u32 { + // Inspired by Substrate https://github.com/paritytech/substrate/blob/c8653447fc8ef8d95a92fe164c96dffb37919e85/primitives/state-machine/src/basic.rs#L242-L254 + let to_remove = self + .state + .range::<[u8], _>((Bound::Included(key_prefix), Bound::Unbounded)) + .map(|(k, _)| k) + .take_while(|k| k.starts_with(key_prefix)) + .cloned() + .collect::>(); + + let count = to_remove.len() as u32; + for key in to_remove { + self.remove(&key); + } + count + } + + fn execute_with(&mut self, f: impl FnOnce() -> R) -> R { + set_and_run_with_externalities(self, f) + } +} + +/// Results concerning an operation to remove many keys. +#[derive(codec::Encode, codec::Decode)] +#[must_use] +pub struct MultiRemovalResults { + /// A continuation cursor which, if `Some` must be provided to the subsequent removal call. + /// If `None` then all removals are complete and no further calls are needed. + pub maybe_cursor: Option>, + /// The number of items removed from the backend database. + pub backend: u32, + /// The number of unique keys removed, taking into account both the backend and the overlay. + pub unique: u32, + /// The number of iterations (each requiring a storage seek/read) which were done. + pub loops: u32, +} + +impl MultiRemovalResults { + /// Deconstruct into the internal components. + /// + /// Returns `(maybe_cursor, backend, unique, loops)`. + pub fn deconstruct(self) -> (Option>, u32, u32, u32) { + (self.maybe_cursor, self.backend, self.unique, self.loops) + } +} + +/// Auxialiary structure for appending a value to a storage item. +/// Taken from https://github.com/paritytech/substrate/blob/master/primitives/state-machine/src/ext.rs +pub(crate) struct StorageAppend<'a>(&'a mut Vec); + +impl<'a> StorageAppend<'a> { + /// Create a new instance using the given `storage` reference. + pub fn new(storage: &'a mut Vec) -> Self { + Self(storage) + } + + /// Append the given `value` to the storage item. + /// + /// If appending fails, `[value]` is stored in the storage item. + pub fn append(&mut self, value: Vec) -> Vec { + let value = vec![EncodeOpaqueValue(value)]; + + let item = core::mem::take(self.0); + + *self.0 = match Vec::::append_or_new(item, &value) { + Ok(item) => item, + Err(_) => { + log::error!("Failed to append value, resetting storage item to input value."); + value.encode() + }, + }; + (*self.0).to_vec() + } +} + +/// Implement `Encode` by forwarding the stored raw vec. +struct EncodeOpaqueValue(Vec); + +impl Encode for EncodeOpaqueValue { + fn using_encoded R>(&self, f: F) -> R { + f(&self.0) + } +} + +#[cfg(test)] +pub mod tests { + + use super::*; + + #[test] + fn mutating_externalities_through_environmental_variable_works() { + let mut externalities = SgxExternalities::default(); + + externalities.execute_with(|| { + with_externalities(|e| { + e.insert("building".encode(), "empire_state".encode()); + e.insert("house".encode(), "ginger_bread".encode()); + }) + .unwrap() + }); + + let state_len = + externalities.execute_with(|| with_externalities(|e| e.state.0.len()).unwrap()); + + assert_eq!(2, state_len); + } + + #[test] + fn basic_externalities_is_empty() { + let ext = SgxExternalities::default(); + assert!(ext.state.0.is_empty()); + } + + #[test] + fn storage_append_works() { + let mut data = Vec::new(); + let mut append = StorageAppend::new(&mut data); + append.append(1u32.encode()); + let updated_data = append.append(2u32.encode()); + drop(append); + + assert_eq!(Vec::::decode(&mut &data[..]).unwrap(), vec![1, 2]); + assert_eq!(updated_data, data); + + // Initialize with some invalid data + let mut data = vec![1]; + let mut append = StorageAppend::new(&mut data); + append.append(1u32.encode()); + append.append(2u32.encode()); + drop(append); + + assert_eq!(Vec::::decode(&mut &data[..]).unwrap(), vec![1, 2]); + } + + #[test] + #[should_panic(expected = "already borrowed: BorrowMutError")] + fn nested_with_externalities_panics() { + let mut ext = SgxExternalities::default(); + + ext.execute_with(|| { + with_externalities(|_| with_externalities(|_| unreachable!("panics before")).unwrap()) + .unwrap(); + }); + } + + #[test] + fn nesting_execute_with_uses_the_latest_externalities() { + let mut ext = SgxExternalities::default(); + let mut ext2 = ext.clone(); + + let hello = b"hello".to_vec(); + let world = b"world".to_vec(); + + ext.execute_with(|| { + with_externalities(|e| { + e.insert(hello.clone(), hello.clone()); + }) + .unwrap(); + + ext2.execute_with(|| { + // `with_externalities` uses the latest set externalities defined by the last + // `set_and_run_with_externalities` call. + with_externalities(|e| { + e.insert(world.clone(), world.clone()); + }) + .unwrap(); + }); + }); + + assert_eq!(ext.get(&hello), Some(&hello)); + assert_eq!(ext2.get(&world), Some(&world)); + + // ext1 and ext2 are unrelated. + assert_eq!(ext.get(&world), None); + } + + #[test] + fn clear_prefix_works() { + let mut externalities = SgxExternalities::default(); + let non_house_key = b"window house".to_vec(); + let non_house_value = b"test_string".to_vec(); + // Fill state. + externalities.execute_with(|| { + with_externalities(|e| { + e.insert(b"house_building".to_vec(), b"empire_state".to_vec()); + e.insert(b"house".to_vec(), b"ginger_bread".to_vec()); + e.insert(b"house door".to_vec(), b"right".to_vec()); + e.insert(non_house_key.clone(), non_house_value.clone()); + }) + .unwrap() + }); + let state_len = + externalities.execute_with(|| with_externalities(|e| e.state.0.len()).unwrap()); + assert_eq!(state_len, 4); + + let number_of_removed_items = externalities + .execute_with(|| with_externalities(|e| e.clear_prefix(b"house", None)).unwrap()); + assert_eq!(number_of_removed_items, 3); + + let state_len = + externalities.execute_with(|| with_externalities(|e| e.state.0.len()).unwrap()); + assert_eq!(state_len, 1); + let stored_value = externalities.execute_with(|| { + with_externalities(|e| { + assert_eq!(e.get(&non_house_key).unwrap().clone(), non_house_value) + }) + }); + assert!(stored_value.is_some()); + } +} diff --git a/tee-worker/core-primitives/substrate-sgx/externalities/src/scope_limited.rs b/tee-worker/core-primitives/substrate-sgx/externalities/src/scope_limited.rs new file mode 100644 index 0000000000..55c9a9e4d7 --- /dev/null +++ b/tee-worker/core-primitives/substrate-sgx/externalities/src/scope_limited.rs @@ -0,0 +1,38 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +//! Stores the externalities in an `environmental` value to make it scope limited available. + +use crate::SgxExternalities; + +environmental::environmental!(ext: SgxExternalities); + +/// Set the given externalities while executing the given closure. To get access to the +/// externalities while executing the given closure [`with_externalities`] grants access to them. +/// The externalities are only set for the same thread this function was called from. +pub fn set_and_run_with_externalities R, R>(ext: &mut SgxExternalities, f: F) -> R { + ext::using(ext, f) +} + +/// Execute the given closure with the currently set externalities. +/// +/// Returns `None` if no externalities are set or `Some(_)` with the result of the closure. +/// +/// Panics with `already borrowed: BorrowMutError` if calls to `with_externalities` are nested. +pub fn with_externalities R, R>(f: F) -> Option { + ext::with(f) +} diff --git a/tee-worker/core-primitives/substrate-sgx/externalities/src/vectorize.rs b/tee-worker/core-primitives/substrate-sgx/externalities/src/vectorize.rs new file mode 100644 index 0000000000..d2203902ae --- /dev/null +++ b/tee-worker/core-primitives/substrate-sgx/externalities/src/vectorize.rs @@ -0,0 +1,76 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +//! Converts maps to vecs for serialization. +//! from https://github.com/DenisKolodin/vectorize + +use serde::{Deserialize, Deserializer, Serialize, Serializer}; +use std::{iter::FromIterator, vec::Vec}; + +pub fn serialize<'a, T, K, V, S>(target: T, ser: S) -> Result +where + S: Serializer, + T: IntoIterator, + K: Serialize + 'a, + V: Serialize + 'a, +{ + let container: Vec<_> = target.into_iter().collect(); + serde::Serialize::serialize(&container, ser) +} + +pub fn deserialize<'de, T, K, V, D>(des: D) -> Result +where + D: Deserializer<'de>, + T: FromIterator<(K, V)>, + K: Deserialize<'de>, + V: Deserialize<'de>, +{ + let container: Vec<_> = serde::Deserialize::deserialize(des)?; + Ok(container.into_iter().collect()) +} + +#[cfg(test)] +mod tests { + use crate::vectorize; + use serde::{Deserialize, Serialize}; + use std::collections::HashMap; + + #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord, Hash)] + struct MyKey { + one: String, + two: u16, + more: Vec, + } + + #[derive(Debug, Serialize, Deserialize)] + struct MyComplexType { + #[serde(with = "vectorize")] + map: HashMap, + } + + #[test] + fn it_works() -> Result<(), Box> { + let key = MyKey { one: "1".into(), two: 2, more: vec![1, 2, 3] }; + let mut map = HashMap::new(); + map.insert(key.clone(), "value".into()); + let instance = MyComplexType { map }; + let serialized = postcard::to_allocvec(&instance)?; + let deserialized: MyComplexType = postcard::from_bytes(&serialized)?; + let expected_value = "value".to_string(); + assert_eq!(deserialized.map.get(&key), Some(&expected_value)); + Ok(()) + } +} diff --git a/tee-worker/core-primitives/substrate-sgx/sp-io/Cargo.toml b/tee-worker/core-primitives/substrate-sgx/sp-io/Cargo.toml new file mode 100644 index 0000000000..92c316d597 --- /dev/null +++ b/tee-worker/core-primitives/substrate-sgx/sp-io/Cargo.toml @@ -0,0 +1,64 @@ +[package] +authors = ["Integritee AG and Parity Technologies "] +edition = "2021" +license = "Apache-2.0" +name = "sp-io" +version = "6.0.0" + +[dependencies] +codec = { version = "3.0.0", package = "parity-scale-codec", default-features = false } +futures = { version = "0.3.1", optional = true, features = ["thread-pool"] } +hash-db = { version = "0.15.2", default-features = false } +libsecp256k1 = { version = "0.7.0", default-features = false, features = ["static-context"] } +log = { version = "0.4", default-features = false } +parking_lot = { version = "0.12.0", optional = true } +tracing = { version = "0.1.25", default-features = false } +tracing-core = { version = "0.1.17", default-features = false } + +itp-sgx-externalities = { default-features = false, path = "../externalities" } +sgx_tstd = { optional = true, features = ["untrusted_fs", "net", "backtrace"], git = "https://github.com/apache/teaclave-sgx-sdk.git", branch = "master" } +sgx_types = { optional = true, git = "https://github.com/apache/teaclave-sgx-sdk.git", branch = "master" } + +# Substrate dependencies +sp-core = { default-features = false, features = ["full_crypto"], git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.29" } +sp-runtime-interface = { default-features = false, git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.29" } +sp-std = { default-features = false, git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.29" } +sp-tracing = { default-features = false, git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.29" } +sp-wasm-interface = { default-features = false, git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.29" } + +# local +environmental = { path = "../environmental", default-features = false } + +[features] +default = ["std"] +sgx = [ + "sgx_tstd", + "sgx_types", + "itp-sgx-externalities/sgx", + "sp-runtime-interface/disable_target_static_assertions", + #local + "environmental/sgx", +] +std = [ + "log/std", + "sp-core/std", + "codec/std", + "sp-std/std", + "hash-db/std", + "libsecp256k1/std", + "sp-runtime-interface/std", + "sp-wasm-interface/std", + "futures", + "parking_lot", + "itp-sgx-externalities/std", + # local + "environmental/std", +] + +# These two features are used for `no_std` builds for the environments which already provides +# `#[panic_handler]`, `#[alloc_error_handler]` and `#[global_allocator]`. +# +# For the regular wasm sgx-runtime builds those are not used. +disable_allocator = [] +disable_oom = [] +disable_panic_handler = [] diff --git a/tee-worker/core-primitives/substrate-sgx/sp-io/src/lib.rs b/tee-worker/core-primitives/substrate-sgx/sp-io/src/lib.rs new file mode 100644 index 0000000000..4787155d9e --- /dev/null +++ b/tee-worker/core-primitives/substrate-sgx/sp-io/src/lib.rs @@ -0,0 +1,1005 @@ +// Copyright 2017-2019 Parity Technologies (UK) Ltd. +// This file is part of Substrate. + +// Substrate is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Substrate is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Substrate. If not, see . + +//#![warn(missing_docs)] + +// Added by Integritee. Prevents warnings during compilation with sgx features at all those +// unimplemented method stubs. +#![allow(unused_variables)] +#![cfg_attr(not(feature = "std"), no_std)] +#![cfg_attr(not(feature = "std"), feature(lang_items))] +#![cfg_attr(not(feature = "std"), feature(alloc_error_handler))] +#![cfg_attr(not(feature = "std"), feature(core_intrinsics))] +#![cfg_attr( + feature = "std", + doc = "Substrate sgx-runtime standard library as compiled when linked with Rust's standard library." +)] +#![cfg_attr( + not(feature = "std"), + doc = "Substrate's sgx-runtime standard library as compiled without Rust's standard library." +)] + +#[cfg(all(feature = "std", feature = "sgx"))] +compile_error!("feature \"std\" and feature \"sgx\" cannot be enabled at the same time"); + +#[cfg(feature = "sgx")] +extern crate sgx_tstd as std; + +use codec::{Decode, Encode}; +use log::*; +use sp_core::{ + crypto::{KeyTypeId, Pair}, + ecdsa, ed25519, + hash::H256, + offchain::{ + HttpError, HttpRequestId, HttpRequestStatus, OpaqueNetworkState, StorageKind, Timestamp, + }, + sr25519, + storage::StateVersion, +}; +use std::{char, prelude::v1::String, println, vec, vec::Vec}; + +#[allow(unused)] +fn encode_hex_digit(digit: u8) -> char { + match char::from_digit(u32::from(digit), 16) { + Some(c) => c, + _ => panic!(), + } +} + +#[allow(unused)] +fn encode_hex_byte(byte: u8) -> [char; 2] { + [encode_hex_digit(byte >> 4), encode_hex_digit(byte & 0x0Fu8)] +} + +#[allow(unused)] +pub fn encode_hex(bytes: &[u8]) -> String { + let strs: Vec = bytes + .iter() + .map(|byte| encode_hex_byte(*byte).iter().copied().collect()) + .collect(); + strs.join("") +} + +// Reexport here, such that the worker does not need to import other crate. +// Not sure if this is a good Idea though. +pub use itp_sgx_externalities::{ + with_externalities, SgxExternalities, SgxExternalitiesTrait, SgxExternalitiesType, +}; + +pub struct MultiRemovalResults { + /// A continuation cursor which, if `Some` must be provided to the subsequent removal call. + /// If `None` then all removals are complete and no further calls are needed. + pub maybe_cursor: Option>, + /// The number of items removed from the backend database. + pub backend: u32, + /// The number of unique keys removed, taking into account both the backend and the overlay. + pub unique: u32, + /// The number of iterations (each requiring a storage seek/read) which were done. + pub loops: u32, +} + +/// Error verifying ECDSA signature +#[derive(Encode, Decode)] +pub enum EcdsaVerifyError { + /// Incorrect value of R or S + BadRS, + /// Incorrect value of V + BadV, + /// Invalid signature + BadSignature, +} + +/// The outcome of calling `storage_kill`. Returned value is the number of storage items +/// removed from the trie from making the `storage_kill` call. +#[derive(Encode, Decode)] +pub enum KillStorageResult { + /// No key remains in the child trie. + AllRemoved(u32), + /// At least one key still resides in the child trie due to the supplied limit. + SomeRemaining(u32), +} + +impl From for KillStorageResult { + fn from(r: MultiRemovalResults) -> Self { + match r { + MultiRemovalResults { maybe_cursor: None, backend, .. } => Self::AllRemoved(backend), + MultiRemovalResults { maybe_cursor: Some(..), backend, .. } => + Self::SomeRemaining(backend), + } + } +} + +pub mod storage { + use super::*; + + pub fn get(key: &[u8]) -> Option> { + debug!("storage('{}')", encode_hex(key)); + with_externalities(|ext| { + ext.get(key).map(|s| { + debug!(" returning {}", encode_hex(s)); + s.to_vec() + }) + }) + .expect("storage cannot be called outside of an Externalities-provided environment.") + } + + pub fn read(key: &[u8], value_out: &mut [u8], value_offset: usize) -> Option { + debug!( + "read_storage('{}' with offset = {:?}. value_out.len() is {})", + encode_hex(key), + value_offset, + value_out.len() + ); + with_externalities(|ext| { + ext.get(key).map(|value| { + debug!(" entire stored value: {:?}", value); + let value = &value[value_offset..]; + debug!(" stored value at offset: {:?}", value); + let written = std::cmp::min(value.len(), value_out.len()); + value_out[..written].copy_from_slice(&value[..written]); + debug!(" write back {:?}, return len {}", value_out, value.len()); + value.len() + }) + }) + .expect("read_storage cannot be called outside of an Externalities-provided environment.") + } + + pub fn set(key: &[u8], value: &[u8]) { + debug!("set_storage('{}', {:x?})", encode_hex(key), value); + with_externalities(|ext| ext.insert(key.to_vec(), value.to_vec())) + .expect("`set` cannot be called outside of an Externalities-provided environment."); + } + + pub fn clear(key: &[u8]) { + with_externalities(|ext| { + if ext.remove(key).is_none() { + info!("Tried to clear storage that was not existing"); + } + }); + } + + pub fn exists(key: &[u8]) -> bool { + with_externalities(|ext| ext.contains_key(key)) + .expect("exists cannot be called outside of an Externalities-provided environment.") + } + + /// Clear the storage of each key-value pair where the key starts with the given `prefix`. + pub fn clear_prefix_version_1(prefix: &[u8]) { + clear_prefix(prefix, None); + } + + /// Clear the storage of each key-value pair where the key starts with the given `prefix`. + /// + /// # Limit + /// + /// Deletes all keys from the overlay and up to `limit` keys from the backend if + /// it is set to `Some`. No limit is applied when `limit` is set to `None`. + /// + /// The limit can be used to partially delete a prefix storage in case it is too large + /// to delete in one go (block). + /// + /// It returns a boolean false iff some keys are remaining in + /// the prefix after the functions returns. Also returns a `u32` with + /// the number of keys removed from the process. + /// + /// # Note + /// + /// Please note that keys that are residing in the overlay for that prefix when + /// issuing this call are all deleted without counting towards the `limit`. Only keys + /// written during the current block are part of the overlay. Deleting with a `limit` + /// mostly makes sense with an empty overlay for that prefix. + /// + /// Calling this function multiple times per block for the same `prefix` does + /// not make much sense because it is not cumulative when called inside the same block. + /// Use this function to distribute the deletion of a single child trie across multiple + /// blocks. + pub fn clear_prefix(prefix: &[u8], maybe_limit: Option) -> KillStorageResult { + let number_of_removed_values = + with_externalities(|ext| ext.clear_prefix(prefix, maybe_limit)).unwrap_or_default(); + KillStorageResult::AllRemoved(number_of_removed_values) + } + + /// Append the encoded `value` to the storage item at `key`. + /// + /// The storage item needs to implement [`EncodeAppend`](codec::EncodeAppend). + /// + /// # Warning + /// + /// If the storage item does not support [`EncodeAppend`](codec::EncodeAppend) or + /// something else fails at appending, the storage item will be set to `[value]`. + pub fn append(key: &[u8], value: Vec) { + with_externalities(|ext| ext.append(key.to_vec(), value.to_vec())); + } + + /// "Commit" all existing operations and compute the resulting storage root. + /// + /// The hashing algorithm is defined by the `Block`. + /// + /// Returns a `Vec` that holds the SCALE encoded hash. + pub fn root_version_1() -> [u8; 32] { + warn!("storage::root() unimplemented"); + [0u8; 32] + } + + /// "Commit" all existing operations and compute the resulting storage root. + /// + /// The hashing algorithm is defined by the `Block`. + /// + /// Returns a `Vec` that holds the SCALE encoded hash. + pub fn root(version: StateVersion) -> [u8; 32] { + warn!("storage::root() unimplemented"); + [0u8; 32] + } + + pub fn changes_root(parent_hash: &[u8]) -> Option<[u8; 32]> { + warn!("storage::changes_root() unimplemented"); + Some([0u8; 32]) + } + + /// Get the next key in storage after the given one in lexicographic order. + pub fn next_key(key: &[u8]) -> Option> { + debug!("next_key('{}')", encode_hex(key)); + with_externalities(|ext| ext.next_storage_key(key)) + .expect("`next_key` cannot be called outside of an Externalities-provided environment.") + } + + /// Start a new nested transaction. + /// + /// This allows to either commit or roll back all changes that are made after this call. + /// For every transaction there must be a matching call to either `rollback_transaction` + /// or `commit_transaction`. This is also effective for all values manipulated using the + /// `DefaultChildStorage` API. + /// + /// # Warning + /// + /// This is a low level API that is potentially dangerous as it can easily result + /// in unbalanced transactions. For example, FRAME users should use high level storage + /// abstractions. + pub fn start_transaction() { + warn!("storage::start_transaction unimplemented"); + } + + /// Rollback the last transaction started by `start_transaction`. + /// + /// Any changes made during that transaction are discarded. + /// + /// # Panics + /// + /// Will panic if there is no open transaction. + pub fn rollback_transaction() { + warn!("storage::rollback_transaction unimplemented"); + } + + /// Commit the last transaction started by `start_transaction`. + /// + /// Any changes made during that transaction are committed. + /// + /// # Panics + /// + /// Will panic if there is no open transaction. + pub fn commit_transaction() { + warn!("storage::commit_transaction unimplemented"); + } +} + +pub mod default_child_storage { + use super::*; + + pub fn read( + storage_key: &[u8], + key: &[u8], + value_out: &mut [u8], + value_offset: u32, + ) -> Option { + // TODO unimplemented + warn!("default_child_storage::read() unimplemented"); + Some(0) + } + + pub fn get(storage_key: &[u8], key: &[u8]) -> Option> { + // TODO: unimplemented + warn!("default_child_storage::get() unimplemented"); + Some(vec![0, 1, 2, 3]) + } + + pub fn set(storage_key: &[u8], key: &[u8], value: &[u8]) { + warn!("default_child_storage::set() unimplemented"); + } + + pub fn clear(storage_key: &[u8], key: &[u8]) { + warn!("child storage::clear() unimplemented"); + } + + pub fn storage_kill_version_1(storage_key: &[u8]) { + warn!("child storage::storage_kill() unimplemented"); + } + + pub fn storage_kill_version_2(storage_key: &[u8], limit: Option) -> bool { + warn!("child storage::storage_kill() unimplemented"); + false + } + + /// Clear a child storage key. + /// + /// See `Storage` module `clear_prefix` documentation for `limit` usage. + pub fn storage_kill(storage_key: &[u8], limit: Option) -> KillStorageResult { + warn!("child storage::storage_kill() unimplemented"); + KillStorageResult::AllRemoved(0) + } + + pub fn exists(storage_key: &[u8], key: &[u8]) -> bool { + warn!("child storage::exists() unimplemented"); + false + } + + /// Clear child default key by prefix. + /// + /// Clear the child storage of each key-value pair where the key starts with the given `prefix`. + pub fn clear_prefix_version_1(storage_key: &[u8], prefix: &[u8]) { + warn!("child storage::clear_prefix() unimplemented"); + } + + /// Clear the child storage of each key-value pair where the key starts with the given `prefix`. + /// + /// See `Storage` module `clear_prefix` documentation for `limit` usage. + pub fn clear_prefix( + storage_key: &[u8], + prefix: &[u8], + limit: Option, + ) -> KillStorageResult { + warn!("child storage::clear_prefix() unimplemented"); + KillStorageResult::AllRemoved(0) + } + + pub fn root_version_1(storage_key: &[u8]) -> Vec { + warn!("child storage::root() unimplemented"); + vec![0, 1, 2, 3] + } + + pub fn root(storage_key: &[u8], version: StateVersion) -> Vec { + warn!("child storage::root() unimplemented"); + vec![0, 1, 2, 3] + } + + pub fn next_key(storage_key: &[u8], key: &[u8]) -> Option> { + warn!("child storage::next_key() unimplemented"); + Some(Vec::new()) + } +} + +pub mod trie { + use super::*; + + /// A trie root formed from the iterated items. + pub fn blake2_256_root_version_1(input: Vec<(Vec, Vec)>) -> H256 { + warn!("trie::blake2_256_root() unimplemented"); + H256::default() + } + + /// A trie root formed from the iterated items. + pub fn blake2_256_root(input: Vec<(Vec, Vec)>, version: StateVersion) -> H256 { + warn!("trie::blake2_256_root() unimplemented"); + H256::default() + } + + /// A trie root formed from the enumerated items. + pub fn blake2_256_ordered_root_version_1(input: Vec>) -> H256 { + warn!("trie::blake2_256_ordered_root() unimplemented"); + H256::default() + } + + /// A trie root formed from the enumerated items. + pub fn blake2_256_ordered_root(input: Vec>, version: StateVersion) -> H256 { + warn!("trie::blake2_256_ordered_root() unimplemented"); + H256::default() + } + + pub fn keccak_256_root_version_1(input: Vec<(Vec, Vec)>) -> H256 { + warn!("trie::keccak_256_root_version_1() unimplemented"); + H256::default() + } + + pub fn keccak_256_root(input: Vec<(Vec, Vec)>, version: StateVersion) -> H256 { + warn!("trie::keccak_256_root() unimplemented"); + H256::default() + } + + /// A trie root formed from the enumerated items. + pub fn keccak_256_ordered_root_version_1(input: Vec>) -> H256 { + warn!("trie::keccak_256_ordered_root() unimplemented"); + H256::default() + } + + /// A trie root formed from the enumerated items. + pub fn keccak_256_ordered_root(input: Vec>, version: StateVersion) -> H256 { + warn!("trie::keccak_256_ordered_root() unimplemented"); + H256::default() + } + + /// Verify trie proof + #[allow(unused)] + fn blake2_256_verify_proof_version_1( + root: H256, + proof: &[Vec], + key: &[u8], + value: &[u8], + ) -> bool { + warn!("trie::blake2_256_verify_proof() unimplemented"); + false + } + + /// Verify trie proof + #[allow(unused)] + fn blake2_256_verify_proof( + root: H256, + proof: &[Vec], + key: &[u8], + value: &[u8], + version: StateVersion, + ) -> bool { + warn!("trie::blake2_256_verify_proof() unimplemented"); + false + } + + /// Verify trie proof + #[allow(unused)] + fn keccak_256_verify_proof_version_1( + root: H256, + proof: &[Vec], + key: &[u8], + value: &[u8], + ) -> bool { + warn!("trie::keccak_256_verify_proof() unimplemented"); + false + } + + /// Verify trie proof + #[allow(unused)] + fn keccak_256_verify_proof( + root: H256, + proof: &[Vec], + key: &[u8], + value: &[u8], + version: StateVersion, + ) -> bool { + warn!("trie::keccak_256_verify_proof() unimplemented"); + false + } +} + +pub mod misc { + use super::*; + /// Print a number. + pub fn print_num(val: u64) { + debug!(target: "sgx-runtime", "{}", val); + } + + /// Print any valid `utf8` buffer. + pub fn print_utf8(utf8: &[u8]) { + if let Ok(data) = std::str::from_utf8(utf8) { + debug!(target: "sgx-runtime", "{}", data) + } + } + + /// Print any `u8` slice as hex. + pub fn print_hex(data: &[u8]) { + debug!(target: "sgx-runtime", "{:?}", data); + } + + pub fn runtime_version(wasm: &[u8]) -> Option> { + warn!("misc::runtime_version unimplemented!"); + Some([2u8; 32].to_vec()) + } +} + +/// Interfaces for working with crypto related types from within the sgx-runtime. +pub mod crypto { + use super::*; + use sp_core::H512; + pub fn ed25519_public_keys(id: KeyTypeId) -> Vec { + warn!("crypto::ed25519_public_keys unimplemented"); + vec![ed25519::Public::from_h256(H256::default())] + } + + pub fn ed25519_generate(id: KeyTypeId, seed: Option>) -> ed25519::Public { + warn!("crypto::ed25519_generate unimplemented"); + ed25519::Public::from_h256(H256::default()) + } + + pub fn ed25519_sign( + id: KeyTypeId, + pub_key: &ed25519::Public, + msg: &[u8], + ) -> Option { + warn!("crypto::ed25519_sign unimplemented"); + + Some(ed25519::Signature::from_raw(H512::default().into())) + } + + pub fn ed25519_verify(sig: &ed25519::Signature, msg: &[u8], pub_key: &ed25519::Public) -> bool { + ed25519::Pair::verify(sig, msg, pub_key) + } + + pub fn ed25519_batch_verify( + sig: &ed25519::Signature, + msg: &[u8], + pub_key: &ed25519::Public, + ) -> bool { + warn!("crypto::ed25519_batch_verify unimplemented"); + false + } + + /// Register a `sr25519` signature for batch verification. + /// + /// Batch verification must be enabled by calling [`start_batch_verify`]. + /// If batch verification is not enabled, the signature will be verified immediatley. + /// To get the result of the batch verification, [`finish_batch_verify`] + /// needs to be called. + /// + /// Returns `true` when the verification is either successful or batched. + pub fn sr25519_batch_verify( + sig: &sr25519::Signature, + msg: &[u8], + pub_key: &sr25519::Public, + ) -> bool { + warn!("crypto::sr25519_batch_verify unimplemented"); + false + } + /// Start verification extension. + pub fn start_batch_verify() { + warn!("crypto::start_batch_verify unimplemented"); + } + + pub fn finish_batch_verify() -> bool { + warn!("crypto::finish_batch_verify unimplemented"); + true + } + + pub fn sr25519_public_keys(id: KeyTypeId) -> Vec { + warn!("crypto::sr25519_public_key unimplemented"); + vec![sr25519::Public::from_h256(H256::default())] + } + + pub fn sr25519_generate(id: KeyTypeId, seed: Option>) -> sr25519::Public { + warn!("crypto::sr25519_generate unimplemented"); + sr25519::Public::from_h256(H256::default()) + } + + pub fn sr25519_sign( + id: KeyTypeId, + pubkey: &sr25519::Public, + msg: &[u8], + ) -> Option { + warn!("crypto::sr25519_sign unimplemented"); + Some(sr25519::Signature::from_raw(H512::default().into())) + } + + /// Verify `sr25519` signature. + /// + /// Returns `true` when the verification was successful. + pub fn sr25519_verify(sig: &sr25519::Signature, msg: &[u8], pub_key: &sr25519::Public) -> bool { + sr25519::Pair::verify(sig, msg, pub_key) + } + + /// Returns all `ecdsa` public keys for the given key id from the keystore. + pub fn ecdsa_public_keys(id: KeyTypeId) -> Vec { + warn!("crypto::ecdsa_public_keys unimplemented"); + Vec::new() + } + + /// Generate an `ecdsa` key for the given key type using an optional `seed` and + /// store it in the keystore. + /// + /// The `seed` needs to be a valid utf8. + /// + /// Returns the public key. + pub fn ecdsa_generate(id: KeyTypeId, seed: Option>) -> ecdsa::Public { + warn!("crypto::ecdsa_generate unimplemented"); + let raw: [u8; 33] = [0; 33]; + ecdsa::Public::from_raw(raw) + } + + /// Sign the given `msg` with the `ecdsa` key that corresponds to the given public key and + /// key type in the keystore. + /// + /// Returns the signature. + pub fn ecdsa_sign( + id: KeyTypeId, + pub_key: &ecdsa::Public, + msg: &[u8], + ) -> Option { + warn!("crypto::ecdsa_sign unimplemented"); + None + } + + /// Verify `ecdsa` signature. + /// + /// Returns `true` when the verification was successful. + pub fn ecdsa_verify(sig: &ecdsa::Signature, msg: &[u8], pub_key: &ecdsa::Public) -> bool { + ecdsa::Pair::verify(sig, msg, pub_key) + } + + /// Register a `ecdsa` signature for batch verification. + /// + /// Batch verification must be enabled by calling [`start_batch_verify`]. + /// If batch verification is not enabled, the signature will be verified immediatley. + /// To get the result of the batch verification, [`finish_batch_verify`] + /// needs to be called. + /// + /// Returns `true` when the verification is either successful or batched. + pub fn ecdsa_batch_verify(sig: &ecdsa::Signature, msg: &[u8], pub_key: &ecdsa::Public) -> bool { + warn!("crypto::ecdsa_batch_verify unimplemented"); + false + } + + pub fn secp256k1_ecdsa_recover( + sig: &[u8; 65], + msg: &[u8; 32], + ) -> Result<[u8; 64], EcdsaVerifyError> { + let rs = libsecp256k1::Signature::parse_standard_slice(&sig[0..64]) + .map_err(|_| EcdsaVerifyError::BadRS)?; + let v = libsecp256k1::RecoveryId::parse(if sig[64] > 26 { sig[64] - 27 } else { sig[64] }) + .map_err(|_| EcdsaVerifyError::BadV)?; + let pubkey = libsecp256k1::recover(&libsecp256k1::Message::parse(msg), &rs, &v) + .map_err(|_| EcdsaVerifyError::BadSignature)?; + let mut res = [0u8; 64]; + res.copy_from_slice(&pubkey.serialize()[1..65]); + + Ok(res) + } + + pub fn secp256k1_ecdsa_recover_compressed( + sig: &[u8; 65], + msg: &[u8; 32], + ) -> Result<[u8; 33], EcdsaVerifyError> { + warn!("crypto::secp256k1_ecdsa_recover unimplemented"); + Ok([0; 33]) + } +} + +/// Interface that provides functions for hashing with different algorithms. +pub mod hashing { + use super::*; + /// Conduct a 256-bit Keccak hash. + pub fn keccak_256(data: &[u8]) -> [u8; 32] { + debug!("keccak_256 of {}", encode_hex(data)); + let hash = sp_core::hashing::keccak_256(data); + debug!(" returning hash {}", encode_hex(&hash)); + hash + } + + /// Conduct a 512-bit Keccak hash. + pub fn keccak_512(data: &[u8]) -> [u8; 64] { + debug!("keccak_512 of {}", encode_hex(data)); + let hash = sp_core::hashing::keccak_512(data); + debug!(" returning hash {}", encode_hex(&hash)); + hash + } + + /// Conduct a 256-bit Sha2 hash. + pub fn sha2_256(data: &[u8]) -> [u8; 32] { + debug!("sha2_256 of {}", encode_hex(data)); + let hash = sp_core::hashing::sha2_256(data); + debug!(" returning hash {}", encode_hex(&hash)); + hash + } + + /// Conduct a 128-bit Blake2 hash. + pub fn blake2_128(data: &[u8]) -> [u8; 16] { + debug!("blake2_128 of {}", encode_hex(data)); + let hash = sp_core::hashing::blake2_128(data); + debug!(" returning hash {}", encode_hex(&hash)); + hash + } + + /// Conduct a 256-bit Blake2 hash. + pub fn blake2_256(data: &[u8]) -> [u8; 32] { + debug!("blake2_256 of {}", encode_hex(data)); + let hash = sp_core::hashing::blake2_256(data); + debug!(" returning hash {}", encode_hex(&hash)); + hash + } + + /// Conduct four XX hashes to give a 256-bit result. + pub fn twox_256(data: &[u8]) -> [u8; 32] { + debug!("twox_256 of {}", encode_hex(data)); + let hash = sp_core::hashing::twox_256(data); + debug!(" returning {}", encode_hex(&hash)); + hash + } + + /// Conduct two XX hashes to give a 128-bit result. + pub fn twox_128(data: &[u8]) -> [u8; 16] { + debug!("twox_128 of {}", encode_hex(data)); + let hash = sp_core::hashing::twox_128(data); + debug!(" returning {}", encode_hex(&hash)); + hash + } + + /// Conduct two XX hashes to give a 64-bit result. + pub fn twox_64(data: &[u8]) -> [u8; 8] { + debug!("twox_64 of {}", encode_hex(data)); + let hash = sp_core::hashing::twox_64(data); + debug!(" returning {}", encode_hex(&hash)); + hash + } +} + +/// Interface that provides transaction indexing API. +pub mod transaction_index { + use super::*; + /// Add transaction index. Returns indexed content hash. + #[allow(unused)] + fn index(extrinsic: u32, size: u32, context_hash: [u8; 32]) { + warn!("transaction_index::index unimplemented"); + } + + /// Conduct a 512-bit Keccak hash. + #[allow(unused)] + fn renew(extrinsic: u32, context_hash: [u8; 32]) { + warn!("transaction_index::renew unimplemented"); + } +} + +pub mod offchain_index { + use super::*; + /// Write a key value pair to the Offchain DB database in a buffered fashion. + pub fn set(key: &[u8], value: &[u8]) { + warn!("offchain_index::set unimplemented"); + } + + /// Remove a key and its associated value from the Offchain DB. + pub fn clear(key: &[u8]) { + warn!("offchain_index::clear unimplemented"); + } +} + +/// Interface that provides functions to access the offchain functionality. +/// +/// These functions are being made available to the sgx-runtime and are called by the sgx-runtime. +pub mod offchain { + use super::*; + + pub fn is_validator() -> bool { + warn!("offchain::is_validator unimplemented"); + false + } + + #[allow(clippy::result_unit_err)] + pub fn submit_transaction(data: Vec) -> Result<(), ()> { + warn!("offchain::submit_transaction unimplemented"); + Err(()) + } + + #[allow(clippy::result_unit_err)] + pub fn network_state() -> Result { + warn!("offchain::network_state unimplemented"); + Err(()) + } + + pub fn timestamp() -> offchain::Timestamp { + warn!("offchain::timestamp unimplemented"); + offchain::Timestamp::default() + } + + pub fn sleep_until(deadline: offchain::Timestamp) { + warn!("offchain::sleep_until unimplemented"); + } + + pub fn random_seed() -> [u8; 32] { + warn!("offchain::random_seed unimplemented"); + [0; 32] + } + + pub fn local_storage_set(kind: offchain::StorageKind, key: &[u8], value: &[u8]) { + warn!("offchain::local_storage_set unimplemented"); + } + pub fn local_storage_clear(kind: StorageKind, key: &[u8]) { + warn!("offchain::local_storage_clear unimplemented"); + } + + pub fn local_storage_compare_and_set( + kind: offchain::StorageKind, + key: &[u8], + old_value: Option>, + new_value: &[u8], + ) -> bool { + warn!("offchain::local_storage_compare_and_set unimplemented"); + false + } + + pub fn local_storage_get(kind: offchain::StorageKind, key: &[u8]) -> Option> { + warn!("offchain::local_storage_get unimplemented"); + None + } + + #[allow(clippy::result_unit_err)] + pub fn http_request_start( + method: &str, + uri: &str, + meta: &[u8], + ) -> Result { + warn!("offchain::http_request_start unimplemented"); + Err(()) + } + + #[allow(clippy::result_unit_err)] + pub fn http_request_add_header( + request_id: offchain::HttpRequestId, + name: &str, + value: &str, + ) -> Result<(), ()> { + warn!("offchain::http_request_add_header unimplemented"); + Err(()) + } + + pub fn http_request_write_body( + request_id: offchain::HttpRequestId, + chunk: &[u8], + deadline: Option, + ) -> Result<(), offchain::HttpError> { + warn!("offchain::http_request_write_body unimplemented"); + Err(offchain::HttpError::IoError) + } + + pub fn http_response_wait( + ids: &[offchain::HttpRequestId], + deadline: Option, + ) -> Vec { + warn!("offchain::http_response_wait unimplemented"); + Vec::new() + } + + pub fn http_response_headers(request_id: offchain::HttpRequestId) -> Vec<(Vec, Vec)> { + warn!("offchain::http_response_wait unimplemented"); + Vec::new() + } + + pub fn http_response_read_body( + request_id: offchain::HttpRequestId, + buffer: &mut [u8], + deadline: Option, + ) -> Result { + warn!("offchain::http_response_read_body unimplemented"); + Err(offchain::HttpError::IoError) + } +} + +/// Interface that provides functions for logging from within the sgx-runtime. +pub mod logging { + use super::*; + use sp_core::{LogLevel, LogLevelFilter}; + /// Request to print a log message on the host. + /// + /// Note that this will be only displayed if the host is enabled to display log messages with + /// given level and target. + /// + /// Instead of using directly, prefer setting up `RuntimeLogger` and using `log` macros. + pub fn log(level: LogLevel, target: &str, message: &[u8]) { + if let Ok(message) = std::str::from_utf8(message) { + // TODO remove this attention boost + println!("\x1b[0;36m[{}]\x1b[0m {}", target, message); + let level = match level { + LogLevel::Error => log::Level::Error, + LogLevel::Warn => log::Level::Warn, + LogLevel::Info => log::Level::Info, + LogLevel::Debug => log::Level::Debug, + LogLevel::Trace => log::Level::Trace, + }; + // FIXME: this logs with target sp_io::logging instead of the provided target! + log::log!(target: target, level, "{}", message,); + } + } + + /// Returns the max log level used by the host. + pub fn max_level() -> LogLevelFilter { + log::max_level().into() + } +} + +mod tracing_setup { + /// Initialize tracing of sp_tracing not necessary – noop. To enable build + /// without std and with the `with-tracing`-feature. + pub fn init_tracing() {} +} + +pub use tracing_setup::init_tracing; + +#[cfg(test)] +mod tests { + use super::*; + use sp_core::storage::well_known_keys::CODE; + + #[test] + fn storage_set_and_retrieve_works() { + let mut ext = SgxExternalities::default(); + + ext.execute_with(|| { + storage::set(b"doe".to_vec().as_slice(), b"reindeer".to_vec().as_slice()); + storage::set(b"dog".to_vec().as_slice(), b"puppy".to_vec().as_slice()); + storage::set(b"dogglesworth".to_vec().as_slice(), b"cat".to_vec().as_slice()); + }); + + ext.execute_with(|| { + assert!(storage::get(b"doe".to_vec().as_slice()).is_some()); + assert!(storage::get(b"dog".to_vec().as_slice()).is_some()); + assert!(storage::get(b"dogglesworth".to_vec().as_slice()).is_some()); + assert!(storage::get(b"boat".to_vec().as_slice()).is_none()); + }); + } + + #[test] + fn externalities_set_and_retrieve_code() { + let mut ext = SgxExternalities::default(); + + let code = vec![1, 2, 3]; + ext.insert(CODE.to_vec(), code.clone()); + + assert_eq!(ext.get(CODE).unwrap(), &code); + } + + #[test] + #[should_panic( + expected = "`set` cannot be called outside of an Externalities-provided environment." + )] + fn storage_set_without_externalities_panics() { + storage::set(b"hello", b"world"); + } + + #[test] + fn storage_set_and_next_key_works() { + let mut ext = SgxExternalities::default(); + + ext.execute_with(|| { + storage::set(b"doe".to_vec().as_slice(), b"reindeer".to_vec().as_slice()); + storage::set(b"dog".to_vec().as_slice(), b"puppy".to_vec().as_slice()); + storage::set(b"dogglesworth".to_vec().as_slice(), b"cat".to_vec().as_slice()); + }); + + ext.execute_with(|| { + assert_eq!(storage::next_key(&[]), Some(b"doe".to_vec())); + assert_eq!(storage::next_key(b"d".to_vec().as_slice()), Some(b"doe".to_vec())); + assert_eq!( + storage::next_key(b"dog".to_vec().as_slice()), + Some(b"dogglesworth".to_vec()) + ); + assert_eq!( + storage::next_key(b"doga".to_vec().as_slice()), + Some(b"dogglesworth".to_vec()) + ); + assert_eq!(storage::next_key(b"dogglesworth".to_vec().as_slice()), None); + assert_eq!(storage::next_key(b"e".to_vec().as_slice()), None); + }); + } + + #[test] + fn storage_next_key_in_empty_externatility_works() { + let mut ext = SgxExternalities::default(); + ext.execute_with(|| { + assert_eq!(storage::next_key(&[]), None); + assert_eq!(storage::next_key(b"dog".to_vec().as_slice()), None); + }); + } + + #[test] + #[should_panic( + expected = "`next_key` cannot be called outside of an Externalities-provided environment." + )] + fn storage_next_key_without_externalities_panics() { + storage::next_key(b"d".to_vec().as_slice()); + } +} diff --git a/tee-worker/core-primitives/teerex-storage/Cargo.toml b/tee-worker/core-primitives/teerex-storage/Cargo.toml new file mode 100644 index 0000000000..25f36bf899 --- /dev/null +++ b/tee-worker/core-primitives/teerex-storage/Cargo.toml @@ -0,0 +1,18 @@ +[package] +authors = ["Integritee AG "] +edition = "2021" +name = "itp-teerex-storage" +version = "0.9.0" + +[dependencies] +sp-std = { default-features = false, git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.29" } + +#local deps +itp-storage = { path = "../storage", default-features = false } + +[features] +default = ["std"] +std = [ + "sp-std/std", + "itp-storage/std", +] diff --git a/tee-worker/core-primitives/teerex-storage/src/lib.rs b/tee-worker/core-primitives/teerex-storage/src/lib.rs new file mode 100644 index 0000000000..706d92fcb1 --- /dev/null +++ b/tee-worker/core-primitives/teerex-storage/src/lib.rs @@ -0,0 +1,35 @@ +#![cfg_attr(not(feature = "std"), no_std)] + +use itp_storage::{storage_map_key, storage_value_key, StorageHasher}; +use sp_std::prelude::Vec; + +pub struct TeeRexStorage; + +// Separate the prefix from the rest because in our case we changed the storage prefix due to +// the rebranding. With the below implementation of the `TeerexStorageKeys`, we could simply +// define another struct `OtherStorage`, implement `StoragePrefix` for it, and get the +// `TeerexStorageKeys` implementation for free. +pub trait StoragePrefix { + fn prefix() -> &'static str; +} + +impl StoragePrefix for TeeRexStorage { + fn prefix() -> &'static str { + "Teerex" + } +} + +pub trait TeerexStorageKeys { + fn enclave_count() -> Vec; + fn enclave(index: u64) -> Vec; +} + +impl TeerexStorageKeys for S { + fn enclave_count() -> Vec { + storage_value_key(Self::prefix(), "EnclaveCount") + } + + fn enclave(index: u64) -> Vec { + storage_map_key(Self::prefix(), "EnclaveRegistry", &index, &StorageHasher::Blake2_128Concat) + } +} diff --git a/tee-worker/core-primitives/test/Cargo.toml b/tee-worker/core-primitives/test/Cargo.toml new file mode 100644 index 0000000000..53fcea5664 --- /dev/null +++ b/tee-worker/core-primitives/test/Cargo.toml @@ -0,0 +1,59 @@ +[package] +edition = "2021" +name = "itp-test" +version = "0.9.0" + +[dependencies] +codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } +derive_more = { version = "0.99.5" } +sgx-crypto-helper = { branch = "master", git = "https://github.com/apache/teaclave-sgx-sdk.git", package = "sgx_crypto_helper", default-features = false } + +# sgx deps +jsonrpc-core_sgx = { package = "jsonrpc-core", git = "https://github.com/scs/jsonrpc", branch = "no_std_v18", default-features = false, optional = true } +sgx_tstd = { branch = "master", git = "https://github.com/apache/teaclave-sgx-sdk.git", optional = true } +sgx_types = { branch = "master", git = "https://github.com/apache/teaclave-sgx-sdk.git" } + +# substrate deps +sp-core = { default-features = false, git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.29" } +sp-runtime = { default-features = false, git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.29" } +sp-std = { default-features = false, git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.29" } + +# local deps +ita-stf = { path = "../../app-libs/stf", default-features = false } +itp-ocall-api = { path = "../ocall-api", default-features = false } +itp-sgx-crypto = { path = "../sgx/crypto", default-features = false } +itp-sgx-externalities = { default-features = false, path = "../substrate-sgx/externalities" } +itp-stf-interface = { path = "../stf-interface", default-features = false } +itp-stf-state-handler = { path = "../stf-state-handler", default-features = false } +itp-storage = { path = "../storage", default-features = false } +itp-teerex-storage = { path = "../teerex-storage", default-features = false } +itp-time-utils = { path = "../time-utils", default-features = false } +itp-types = { path = "../types", default-features = false, features = ["test"] } + + +[features] +default = ["std"] +sgx = [ + "ita-stf/sgx", + "itp-sgx-crypto/sgx", + "itp-sgx-externalities/sgx", + "itp-stf-state-handler/sgx", + "itp-time-utils/sgx", + "jsonrpc-core_sgx", + "sgx_tstd", +] +std = [ + "codec/std", + "itp-sgx-crypto/std", + "itp-sgx-externalities/std", + "itp-stf-interface/std", + "itp-stf-state-handler/std", + "itp-storage/std", + "itp-teerex-storage/std", + "itp-time-utils/std", + "itp-types/std", + "itp-ocall-api/std", + "sp-core/std", + "sp-runtime/std", + "sp-std/std", +] diff --git a/tee-worker/core-primitives/test/src/builders/enclave_gen_builder.rs b/tee-worker/core-primitives/test/src/builders/enclave_gen_builder.rs new file mode 100644 index 0000000000..d5a48aa20c --- /dev/null +++ b/tee-worker/core-primitives/test/src/builders/enclave_gen_builder.rs @@ -0,0 +1,63 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + Copyright (C) 2017-2019 Baidu, Inc. All Rights Reserved. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +use itp_time_utils::now_as_u64; +use itp_types::{EnclaveGen, PalletString}; + +/// Builder for a generic enclave (`EnclaveGen`) struct. +pub struct EnclaveGenBuilder { + pubkey: AccountId, + mr_enclave: [u8; 32], + timestamp: u64, + url: PalletString, // utf8 encoded url +} + +impl Default for EnclaveGenBuilder +where + AccountId: Default, +{ + fn default() -> Self { + EnclaveGenBuilder { + pubkey: AccountId::default(), + mr_enclave: [0u8; 32], + timestamp: now_as_u64(), + url: PalletString::default(), + } + } +} + +impl EnclaveGenBuilder { + pub fn with_account(mut self, account: AccountId) -> Self { + self.pubkey = account; + self + } + + pub fn with_url(mut self, url: PalletString) -> Self { + self.url = url; + self + } + + pub fn build(self) -> EnclaveGen { + EnclaveGen { + pubkey: self.pubkey, + mr_enclave: self.mr_enclave, + timestamp: self.timestamp, + url: self.url, + } + } +} diff --git a/tee-worker/core-primitives/test/src/builders/mod.rs b/tee-worker/core-primitives/test/src/builders/mod.rs new file mode 100644 index 0000000000..610066f015 --- /dev/null +++ b/tee-worker/core-primitives/test/src/builders/mod.rs @@ -0,0 +1,21 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + Copyright (C) 2017-2019 Baidu, Inc. All Rights Reserved. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +//! Builder patterns for common structs used in tests. + +pub mod enclave_gen_builder; diff --git a/tee-worker/core-primitives/test/src/lib.rs b/tee-worker/core-primitives/test/src/lib.rs new file mode 100644 index 0000000000..7ce2315298 --- /dev/null +++ b/tee-worker/core-primitives/test/src/lib.rs @@ -0,0 +1,36 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + Copyright (C) 2017-2019 Baidu, Inc. All Rights Reserved. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +#![cfg_attr(not(feature = "std"), no_std)] + +//! Itp-test crate which contains mocks and soon some fixtures. + +#[cfg(all(feature = "std", feature = "sgx"))] +compile_error!("feature \"std\" and feature \"sgx\" cannot be enabled at the same time"); + +#[cfg(all(not(feature = "std"), feature = "sgx"))] +extern crate sgx_tstd as std; + +// re-export module to properly feature gate sgx and regular std environment +#[cfg(all(not(feature = "std"), feature = "sgx"))] +pub mod sgx_reexport_prelude { + pub use jsonrpc_core_sgx as jsonrpc_core; +} + +pub mod builders; +pub mod mock; diff --git a/tee-worker/core-primitives/test/src/mock/handle_state_mock.rs b/tee-worker/core-primitives/test/src/mock/handle_state_mock.rs new file mode 100644 index 0000000000..6d4cf79ede --- /dev/null +++ b/tee-worker/core-primitives/test/src/mock/handle_state_mock.rs @@ -0,0 +1,203 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +#[cfg(feature = "sgx")] +use std::sync::{SgxRwLock as RwLock, SgxRwLockWriteGuard as RwLockWriteGuard}; + +#[cfg(feature = "std")] +use std::sync::{RwLock, RwLockWriteGuard}; + +use codec::Encode; +use ita_stf::State as StfState; +use itp_stf_state_handler::{ + error::{Error, Result}, + handle_state::HandleState, + query_shard_state::QueryShardState, +}; +use itp_types::{ShardIdentifier, H256}; +use sp_core::blake2_256; +use std::{collections::HashMap, format, vec::Vec}; + +/// Mock implementation for the `HandleState` trait. +/// +/// Uses an in-memory state, in a `HashMap`. To be used in unit tests. +#[derive(Default)] +pub struct HandleStateMock { + state_map: RwLock>, +} + +impl HandleStateMock { + pub fn from_shard(shard: ShardIdentifier) -> Result { + let state_handler = HandleStateMock { state_map: Default::default() }; + state_handler.initialize_shard(shard)?; + Ok(state_handler) + } +} + +impl HandleState for HandleStateMock { + type WriteLockPayload = HashMap; + type StateT = StfState; + type HashType = H256; + + fn initialize_shard(&self, shard: ShardIdentifier) -> Result { + self.reset(StfState::default(), &shard) + } + + fn load(&self, shard: &ShardIdentifier) -> Result { + self.state_map + .read() + .unwrap() + .get(shard) + .cloned() + .ok_or_else(|| Error::Other(format!("shard is not initialized {:?}", shard).into())) + } + + fn load_for_mutation( + &self, + shard: &ShardIdentifier, + ) -> Result<(RwLockWriteGuard<'_, Self::WriteLockPayload>, StfState)> { + let initialized_state = self.load(shard)?; + let write_lock = self.state_map.write().unwrap(); + Ok((write_lock, initialized_state)) + } + + fn write_after_mutation( + &self, + state: StfState, + mut state_lock: RwLockWriteGuard<'_, Self::WriteLockPayload>, + shard: &ShardIdentifier, + ) -> Result { + state_lock.insert(*shard, state.clone()); + Ok(state.using_encoded(blake2_256).into()) + } + + fn reset(&self, state: Self::StateT, shard: &ShardIdentifier) -> Result { + let write_lock = self.state_map.write().unwrap(); + self.write_after_mutation(state, write_lock, shard) + } +} + +impl QueryShardState for HandleStateMock { + fn shard_exists(&self, shard: &ShardIdentifier) -> Result { + let state_map_lock = self.state_map.read().map_err(|_| Error::LockPoisoning)?; + Ok(state_map_lock.get(shard).is_some()) + } + + fn list_shards(&self) -> Result> { + Ok(self.state_map.read().unwrap().iter().map(|(k, _)| *k).collect()) + } +} + +// Since the mock itself has quite a bit of complexity, we also have tests for the mock. +#[cfg(feature = "sgx")] +pub mod tests { + + use super::*; + use codec::{Decode, Encode}; + use ita_stf::stf_sgx_tests::StfState; + use itp_sgx_externalities::{SgxExternalitiesTrait, SgxExternalitiesType}; + use itp_stf_interface::InitState; + use itp_types::ShardIdentifier; + use sp_core::{blake2_256, crypto::AccountId32}; + + pub fn initialized_shards_list_is_empty() { + let state_handler = HandleStateMock::default(); + assert!(state_handler.list_shards().unwrap().is_empty()); + } + + pub fn shard_exists_after_inserting() { + let state_handler = HandleStateMock::default(); + let shard = ShardIdentifier::default(); + state_handler.initialize_shard(shard).unwrap(); + + assert!(state_handler.load(&shard).is_ok()); + assert!(state_handler.shard_exists(&shard).unwrap()); + } + + pub fn from_shard_works() { + let shard = ShardIdentifier::default(); + let state_handler = HandleStateMock::from_shard(shard).unwrap(); + + assert!(state_handler.load(&shard).is_ok()); + assert!(state_handler.shard_exists(&shard).unwrap()); + } + + pub fn initialize_creates_default_state() { + let state_handler = HandleStateMock::default(); + let shard = ShardIdentifier::default(); + state_handler.initialize_shard(shard).unwrap(); + + let loaded_state_result = state_handler.load(&shard); + + assert!(loaded_state_result.is_ok()); + } + + pub fn load_mutate_and_write_works() { + let state_handler = HandleStateMock::default(); + let shard = ShardIdentifier::default(); + state_handler.initialize_shard(shard).unwrap(); + + let (lock, mut state) = state_handler.load_for_mutation(&shard).unwrap(); + + let (key, value) = ("my_key", "my_value"); + state.insert(key.encode(), value.encode()); + + state_handler.write_after_mutation(state, lock, &shard).unwrap(); + + let updated_state = state_handler.load(&shard).unwrap(); + + let inserted_value = + updated_state.get(key.encode().as_slice()).expect("value for key should exist"); + assert_eq!(*inserted_value, value.encode()); + } + + pub fn ensure_subsequent_state_loads_have_same_hash() { + let state_handler = HandleStateMock::default(); + let shard = ShardIdentifier::default(); + state_handler.initialize_shard(shard).unwrap(); + + let (lock, _) = state_handler.load_for_mutation(&shard).unwrap(); + let initial_state = StfState::init_state(AccountId32::new([0u8; 32])); + let state_hash_before_execution = hash_of(&initial_state.state); + state_handler.write_after_mutation(initial_state, lock, &shard).unwrap(); + + let state_loaded = state_handler.load(&shard).unwrap(); + let loaded_state_hash = hash_of(&state_loaded.state); + + assert_eq!(state_hash_before_execution, loaded_state_hash); + } + + pub fn ensure_encode_and_encrypt_does_not_affect_state_hash() { + let state = StfState::init_state(AccountId32::new([0u8; 32])); + let state_hash_before_execution = hash_of(&state.state); + + let encoded_state = state.state.encode(); + let decoded_state: SgxExternalitiesType = decode(encoded_state); + + let decoded_state_hash = hash_of(&decoded_state); + + assert_eq!(state_hash_before_execution, decoded_state_hash); + } + + fn hash_of(encodable: &T) -> H256 { + encodable.using_encoded(blake2_256).into() + } + + fn decode(encoded: Vec) -> T { + T::decode(&mut encoded.as_slice()).unwrap() + } +} diff --git a/tee-worker/core-primitives/test/src/mock/metrics_ocall_mock.rs b/tee-worker/core-primitives/test/src/mock/metrics_ocall_mock.rs new file mode 100644 index 0000000000..14cd8e67c5 --- /dev/null +++ b/tee-worker/core-primitives/test/src/mock/metrics_ocall_mock.rs @@ -0,0 +1,54 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +#[cfg(feature = "sgx")] +use std::sync::SgxRwLock as RwLock; + +#[cfg(feature = "std")] +use std::sync::RwLock; + +use codec::Encode; +use itp_ocall_api::EnclaveMetricsOCallApi; +use sgx_types::SgxResult; +use std::vec::Vec; + +/// Metrics o-call mock. +#[derive(Default)] +pub struct MetricsOCallMock { + metric_updates: RwLock>>, +} + +impl Clone for MetricsOCallMock { + fn clone(&self) -> Self { + MetricsOCallMock { + metric_updates: RwLock::new(self.metric_updates.read().unwrap().clone()), + } + } +} + +impl MetricsOCallMock { + pub fn get_metrics_updates(&self) -> Vec> { + self.metric_updates.read().unwrap().clone() + } +} + +impl EnclaveMetricsOCallApi for MetricsOCallMock { + fn update_metric(&self, metric: Metric) -> SgxResult<()> { + self.metric_updates.write().unwrap().push(metric.encode()); + Ok(()) + } +} diff --git a/tee-worker/core-primitives/test/src/mock/mod.rs b/tee-worker/core-primitives/test/src/mock/mod.rs new file mode 100644 index 0000000000..2d91b77ff3 --- /dev/null +++ b/tee-worker/core-primitives/test/src/mock/mod.rs @@ -0,0 +1,22 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +pub mod handle_state_mock; +pub mod metrics_ocall_mock; +pub mod onchain_mock; +pub mod shielding_crypto_mock; +pub mod sidechain_ocall_api_mock; diff --git a/tee-worker/core-primitives/test/src/mock/onchain_mock.rs b/tee-worker/core-primitives/test/src/mock/onchain_mock.rs new file mode 100644 index 0000000000..bebdbf28e0 --- /dev/null +++ b/tee-worker/core-primitives/test/src/mock/onchain_mock.rs @@ -0,0 +1,216 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + Copyright (C) 2017-2019 Baidu, Inc. All Rights Reserved. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +use codec::{Decode, Encode}; +use core::fmt::Debug; +use itp_ocall_api::{ + EnclaveAttestationOCallApi, EnclaveMetricsOCallApi, EnclaveOnChainOCallApi, + EnclaveSidechainOCallApi, +}; +use itp_storage::Error::StorageValueUnavailable; +use itp_teerex_storage::{TeeRexStorage, TeerexStorageKeys}; +use itp_types::{ + storage::StorageEntryVerified, BlockHash, Enclave, ShardIdentifier, WorkerRequest, + WorkerResponse, +}; +use sgx_types::{ + sgx_epid_group_id_t, sgx_measurement_t, sgx_platform_info_t, sgx_quote_nonce_t, + sgx_quote_sign_type_t, sgx_report_t, sgx_spid_t, sgx_target_info_t, sgx_update_info_bit_t, + SgxResult, SGX_HASH_SIZE, +}; +use sp_core::H256; +use sp_runtime::{traits::Header as HeaderTrait, AccountId32, OpaqueExtrinsic}; +use sp_std::prelude::*; +use std::collections::HashMap; + +#[derive(Default, Clone, Debug)] +pub struct OnchainMock { + inner: HashMap, Vec>, + mr_enclave: [u8; SGX_HASH_SIZE], +} + +impl OnchainMock { + pub fn with_storage_entries_at_header, V: Encode>( + mut self, + header: &Header, + entries: Vec<(Vec, V)>, + ) -> Self { + for (key, value) in entries.into_iter() { + self.insert_at_header(header, key, value.encode()); + } + self + } + + pub fn add_validateer_set>( + mut self, + header: &Header, + set: Option>, + ) -> Self { + let set = set.unwrap_or_else(validateer_set); + self.insert_at_header(header, TeeRexStorage::enclave_count(), (set.len() as u64).encode()); + self.with_storage_entries_at_header(header, into_key_value_storage(set)) + } + + pub fn with_mr_enclave(mut self, mr_enclave: [u8; SGX_HASH_SIZE]) -> Self { + self.mr_enclave = mr_enclave; + self + } + + pub fn insert_at_header>( + &mut self, + header: &Header, + key: Vec, + value: Vec, + ) { + let key_with_header = (header, key).encode(); + self.inner.insert(key_with_header, value); + } + + pub fn get_at_header>( + &self, + header: &Header, + key: &[u8], + ) -> Option<&Vec> { + let key_with_header = (header, key).encode(); + self.inner.get(&key_with_header) + } +} + +impl EnclaveAttestationOCallApi for OnchainMock { + fn sgx_init_quote(&self) -> SgxResult<(sgx_target_info_t, sgx_epid_group_id_t)> { + todo!() + } + + fn get_ias_socket(&self) -> SgxResult { + Ok(42) + } + + fn get_quote( + &self, + _sig_rl: Vec, + _report: sgx_report_t, + _sign_type: sgx_quote_sign_type_t, + _spid: sgx_spid_t, + _quote_nonce: sgx_quote_nonce_t, + ) -> SgxResult<(sgx_report_t, Vec)> { + todo!() + } + + fn get_update_info( + &self, + _platform_info: sgx_platform_info_t, + _enclave_trusted: i32, + ) -> SgxResult { + todo!() + } + + fn get_mrenclave_of_self(&self) -> SgxResult { + Ok(sgx_measurement_t { m: self.mr_enclave }) + } +} + +impl EnclaveSidechainOCallApi for OnchainMock { + fn propose_sidechain_blocks( + &self, + _signed_blocks: Vec, + ) -> SgxResult<()> { + Ok(()) + } + + fn store_sidechain_blocks( + &self, + _signed_blocks: Vec, + ) -> SgxResult<()> { + Ok(()) + } + + fn fetch_sidechain_blocks_from_peer( + &self, + _last_imported_block_hash: BlockHash, + _maybe_until_block_hash: Option, + _shard_identifier: ShardIdentifier, + ) -> SgxResult> { + Ok(Vec::new()) + } +} + +impl EnclaveMetricsOCallApi for OnchainMock { + fn update_metric(&self, _metric: Metric) -> SgxResult<()> { + Ok(()) + } +} + +impl EnclaveOnChainOCallApi for OnchainMock { + fn send_to_parentchain(&self, _extrinsics: Vec) -> SgxResult<()> { + Ok(()) + } + + fn worker_request( + &self, + _req: Vec, + ) -> SgxResult>> { + Ok(Vec::new()) + } + + fn get_storage_verified, V: Decode>( + &self, + storage_hash: Vec, + header: &Header, + ) -> Result, itp_ocall_api::Error> { + self.get_multiple_storages_verified(vec![storage_hash], header)? + .into_iter() + .next() + .ok_or_else(|| itp_ocall_api::Error::Storage(StorageValueUnavailable)) + } + + fn get_multiple_storages_verified, V: Decode>( + &self, + storage_hashes: Vec>, + header: &Header, + ) -> Result>, itp_ocall_api::Error> { + let mut entries = Vec::with_capacity(storage_hashes.len()); + for hash in storage_hashes.into_iter() { + let value = self + .get_at_header(header, &hash) + .map(|val| Decode::decode(&mut val.as_slice())) + .transpose() + .map_err(itp_ocall_api::Error::Codec)?; + + entries.push(StorageEntryVerified::new(hash, value)) + } + Ok(entries) + } +} + +pub fn validateer_set() -> Vec { + let default_enclave = Enclave::new( + AccountId32::from([0; 32]), + Default::default(), + Default::default(), + Default::default(), + ); + vec![default_enclave.clone(), default_enclave.clone(), default_enclave.clone(), default_enclave] +} + +fn into_key_value_storage(validateers: Vec) -> Vec<(Vec, Enclave)> { + validateers + .into_iter() + .enumerate() + .map(|(i, e)| (TeeRexStorage::enclave(i as u64 + 1), e)) + .collect() +} diff --git a/tee-worker/core-primitives/test/src/mock/shielding_crypto_mock.rs b/tee-worker/core-primitives/test/src/mock/shielding_crypto_mock.rs new file mode 100644 index 0000000000..441c770833 --- /dev/null +++ b/tee-worker/core-primitives/test/src/mock/shielding_crypto_mock.rs @@ -0,0 +1,62 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +use itp_sgx_crypto::{ + ed25519_derivation::DeriveEd25519, ShieldingCryptoDecrypt, ShieldingCryptoEncrypt, +}; +use sgx_crypto_helper::{rsa3072::Rsa3072KeyPair, RsaKeyPair}; +use sp_core::ed25519::Pair as Ed25519Pair; +use std::vec::Vec; + +/// Crypto key mock +/// +/// mock implementation that does not encrypt +/// encrypt/decrypt return the input as is +#[derive(Clone)] +pub struct ShieldingCryptoMock { + key: Rsa3072KeyPair, +} + +impl Default for ShieldingCryptoMock { + fn default() -> Self { + ShieldingCryptoMock { + key: Rsa3072KeyPair::new().expect("default RSA3072 key for shielding key mock"), + } + } +} + +impl ShieldingCryptoEncrypt for ShieldingCryptoMock { + type Error = itp_sgx_crypto::Error; + + fn encrypt(&self, data: &[u8]) -> Result, Self::Error> { + self.key.encrypt(data) + } +} + +impl ShieldingCryptoDecrypt for ShieldingCryptoMock { + type Error = itp_sgx_crypto::Error; + + fn decrypt(&self, data: &[u8]) -> Result, Self::Error> { + self.key.decrypt(data) + } +} + +impl DeriveEd25519 for ShieldingCryptoMock { + fn derive_ed25519(&self) -> Result { + self.key.derive_ed25519() + } +} diff --git a/tee-worker/core-primitives/test/src/mock/sidechain_ocall_api_mock.rs b/tee-worker/core-primitives/test/src/mock/sidechain_ocall_api_mock.rs new file mode 100644 index 0000000000..28be58f44d --- /dev/null +++ b/tee-worker/core-primitives/test/src/mock/sidechain_ocall_api_mock.rs @@ -0,0 +1,110 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +#[cfg(feature = "sgx")] +use std::sync::SgxRwLock as RwLock; + +#[cfg(feature = "std")] +use std::sync::RwLock; + +use codec::{Decode, Encode}; +use core::marker::PhantomData; +use itp_ocall_api::EnclaveSidechainOCallApi; +use itp_types::{BlockHash, ShardIdentifier}; +use sgx_types::{sgx_status_t, SgxResult}; +use std::vec::Vec; + +pub struct SidechainOCallApiMock { + fetch_from_peer_blocks: Option>, + number_of_fetch_calls: RwLock, + _phantom: PhantomData, +} + +impl SidechainOCallApiMock +where + SignedSidechainBlockType: Clone + Encode + Decode + Send + Sync, +{ + pub fn with_peer_fetch_blocks(mut self, blocks: Vec) -> Self { + self.fetch_from_peer_blocks = Some(blocks); + self + } + + pub fn number_of_fetch_calls(&self) -> usize { + *self.number_of_fetch_calls.read().unwrap() + } +} + +impl Default for SidechainOCallApiMock { + fn default() -> Self { + SidechainOCallApiMock { + fetch_from_peer_blocks: None, + number_of_fetch_calls: RwLock::new(0), + _phantom: Default::default(), + } + } +} + +impl Clone for SidechainOCallApiMock +where + SignedSidechainBlockType: Clone + Encode + Decode + Send + Sync, +{ + fn clone(&self) -> Self { + SidechainOCallApiMock { + fetch_from_peer_blocks: self.fetch_from_peer_blocks.clone(), + number_of_fetch_calls: RwLock::new(*self.number_of_fetch_calls.read().unwrap()), + _phantom: self._phantom, + } + } +} + +impl EnclaveSidechainOCallApi + for SidechainOCallApiMock +where + SignedSidechainBlockType: Clone + Encode + Decode + Send + Sync, +{ + fn propose_sidechain_blocks( + &self, + _signed_blocks: Vec, + ) -> SgxResult<()> { + Ok(()) + } + + fn store_sidechain_blocks( + &self, + _signed_blocks: Vec, + ) -> SgxResult<()> { + Ok(()) + } + + fn fetch_sidechain_blocks_from_peer( + &self, + _last_imported_block_hash: BlockHash, + _maybe_until_block_hash: Option, + _shard_identifier: ShardIdentifier, + ) -> SgxResult> { + let mut number_of_fetch_calls_lock = self.number_of_fetch_calls.write().unwrap(); + *number_of_fetch_calls_lock += 1; + + match &self.fetch_from_peer_blocks { + Some(blocks) => Ok(blocks + .iter() + .map(|b| SignedSidechainBlock::decode(&mut b.encode().as_slice()).unwrap()) + .collect()), + None => Err(sgx_status_t::SGX_ERROR_UNEXPECTED), + } + } +} diff --git a/tee-worker/core-primitives/time-utils/Cargo.toml b/tee-worker/core-primitives/time-utils/Cargo.toml new file mode 100644 index 0000000000..3931a4ea36 --- /dev/null +++ b/tee-worker/core-primitives/time-utils/Cargo.toml @@ -0,0 +1,16 @@ +[package] +authors = ["Integritee AG "] +edition = "2021" +name = "itp-time-utils" +version = "0.9.0" + +[dependencies] +sgx_tstd = { branch = "master", git = "https://github.com/apache/teaclave-sgx-sdk.git", optional = true } + +[features] +default = ["std"] +sgx = [ + "sgx_tstd", +] +std = [ +] diff --git a/tee-worker/core-primitives/time-utils/src/lib.rs b/tee-worker/core-primitives/time-utils/src/lib.rs new file mode 100644 index 0000000000..ee420069c0 --- /dev/null +++ b/tee-worker/core-primitives/time-utils/src/lib.rs @@ -0,0 +1,58 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ +//! General time utility functions. +#![feature(trait_alias)] +#![cfg_attr(not(feature = "std"), no_std)] + +#[cfg(all(feature = "std", feature = "sgx"))] +compile_error!("feature \"std\" and feature \"sgx\" cannot be enabled at the same time"); + +#[cfg(all(not(feature = "std"), feature = "sgx"))] +extern crate sgx_tstd as std; + +use std::time::{Duration, SystemTime}; + +/// Returns current duration since unix epoch in millis as u64. +pub fn now_as_u64() -> u64 { + duration_now().as_millis() as u64 +} + +/// Returns the current timestamp based on the unix epoch in nanoseconds. +pub fn now_as_nanos() -> u128 { + duration_now().as_nanos() +} + +/// Calculates the remaining time from now to `until`. +pub fn remaining_time(until: Duration) -> Option { + duration_difference(duration_now(), until) +} + +/// Calculate the difference in duration between `from` and `to`. +/// Returns `None` if `to` < `from`. +pub fn duration_difference(from: Duration, to: Duration) -> Option { + to.checked_sub(from) +} + +/// Returns current duration since unix epoch with SystemTime::now(). +/// Note: subsequent calls are not guaranteed to be monotonic. +/// (https://doc.rust-lang.org/std/time/struct.SystemTime.html) +pub fn duration_now() -> Duration { + let now = SystemTime::now(); + now.duration_since(SystemTime::UNIX_EPOCH).unwrap_or_else(|e| { + panic!("Current time {:?} is before unix epoch. Something is wrong: {:?}", now, e) + }) +} diff --git a/tee-worker/core-primitives/top-pool-author/Cargo.toml b/tee-worker/core-primitives/top-pool-author/Cargo.toml new file mode 100644 index 0000000000..7a0b99dd07 --- /dev/null +++ b/tee-worker/core-primitives/top-pool-author/Cargo.toml @@ -0,0 +1,76 @@ +[package] +authors = ["Integritee AG "] +edition = "2021" +name = "itp-top-pool-author" +version = "0.9.0" + +[dependencies] +# sgx dependencies +sgx_tstd = { branch = "master", git = "https://github.com/apache/teaclave-sgx-sdk.git", optional = true } +sgx_types = { branch = "master", git = "https://github.com/apache/teaclave-sgx-sdk.git" } + +# local dependencies +ita-stf = { path = "../../app-libs/stf", default-features = false } +itp-enclave-metrics = { path = "../enclave-metrics", default-features = false } +itp-ocall-api = { path = "../ocall-api", default-features = false } +itp-sgx-crypto = { path = "../sgx/crypto", default-features = false } +itp-stf-state-handler = { path = "../stf-state-handler", default-features = false } +itp-test = { path = "../test", default-features = false, optional = true } +itp-top-pool = { path = "../top-pool", default-features = false } +itp-types = { path = "../types", default-features = false } +itp-utils = { path = "../utils", default-features = false } + +# sgx enabled external libraries +jsonrpc-core_sgx = { package = "jsonrpc-core", git = "https://github.com/scs/jsonrpc", branch = "no_std_v18", default-features = false, optional = true } +thiserror_sgx = { package = "thiserror", git = "https://github.com/mesalock-linux/thiserror-sgx", tag = "sgx_1.1.3", optional = true } + +# std compatible external libraries (make sure these versions match with the sgx-enabled ones above) +jsonrpc-core = { version = "18", optional = true } +thiserror = { version = "1.0", optional = true } + +# no-std compatible libraries +codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } +derive_more = { version = "0.99.5" } +log = { version = "0.4", default-features = false } +sp-core = { default-features = false, features = ["full_crypto"], git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.29" } +sp-runtime = { default-features = false, git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.29" } + +[dev-dependencies] +futures = { version = "0.3" } +itp-sgx-crypto = { path = "../sgx/crypto", features = ["mocks"] } +itp-test = { path = "../test" } +itp-top-pool = { path = "../top-pool", features = ["mocks"] } +sgx-crypto-helper = { branch = "master", git = "https://github.com/apache/teaclave-sgx-sdk.git", package = "sgx_crypto_helper", default-features = false } +sp-keyring = { git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.29" } + +[features] +default = ["std"] +mocks = [] +offchain-worker = [] +sgx = [ + "sgx_tstd", + "jsonrpc-core_sgx", + "ita-stf/sgx", + "itp-enclave-metrics/sgx", + "itp-sgx-crypto/sgx", + "itp-stf-state-handler/sgx", + "itp-top-pool/sgx", + "itp-utils/sgx", + "thiserror_sgx", +] +sidechain = [] +std = [ + "ita-stf/std", + "itp-sgx-crypto/std", + "itp-enclave-metrics/std", + "itp-ocall-api/std", + "itp-stf-state-handler/std", + "itp-top-pool/std", + "itp-types/std", + "itp-utils/std", + "jsonrpc-core", + "log/std", + "thiserror", +] +teeracle = [] +test = ["itp-test/sgx", "itp-top-pool/mocks"] diff --git a/tee-worker/core-primitives/top-pool-author/src/api.rs b/tee-worker/core-primitives/top-pool-author/src/api.rs new file mode 100644 index 0000000000..708871b394 --- /dev/null +++ b/tee-worker/core-primitives/top-pool-author/src/api.rs @@ -0,0 +1,200 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +//! Chain api required for the operation pool. + +#[cfg(all(not(feature = "std"), feature = "sgx"))] +use crate::sgx_reexport_prelude::*; + +use crate::error; +use codec::Encode; +use ita_stf::{ + Getter, ShardIdentifier, TrustedCallSigned, TrustedOperation as StfTrustedOperation, +}; +use itp_top_pool::{ + pool::{ChainApi, ExtrinsicHash, NumberFor}, + primitives::TrustedOperationSource, +}; +use itp_types::BlockHash as SidechainBlockHash; +use jsonrpc_core::futures::future::{ready, Future, Ready}; +use log::*; +use sp_runtime::{ + generic::BlockId, + traits::{Block as BlockT, Hash as HashT, Header as HeaderT}, + transaction_validity::{ + TransactionValidity, TransactionValidityError, UnknownTransaction, ValidTransaction, + }, +}; +use std::{boxed::Box, marker::PhantomData, pin::Pin, vec, vec::Vec}; + +/// Future that resolves to account nonce. +pub type Result = core::result::Result; + +/// The operation pool logic for full client. +pub struct SidechainApi { + _marker: PhantomData, +} + +impl SidechainApi { + /// Create new operation pool logic. + pub fn new() -> Self { + SidechainApi { _marker: Default::default() } + } + + fn validate_trusted_call(trusted_call_signed: TrustedCallSigned) -> ValidTransaction { + let from = trusted_call_signed.call.sender_account(); + let requires = vec![]; + let provides = vec![(from, trusted_call_signed.nonce).encode()]; + + ValidTransaction { priority: 1 << 20, requires, provides, longevity: 64, propagate: true } + } +} + +impl Default for SidechainApi { + fn default() -> Self { + Self::new() + } +} + +impl ChainApi for SidechainApi +where + Block: BlockT, +{ + type Block = Block; + type Error = error::Error; + type ValidationFuture = + Pin> + Send>>; + type BodyFuture = Ready>>>; + + fn validate_transaction( + &self, + _source: TrustedOperationSource, + uxt: StfTrustedOperation, + _shard: ShardIdentifier, + ) -> Self::ValidationFuture { + let operation = match uxt { + StfTrustedOperation::direct_call(signed_call) => + Self::validate_trusted_call(signed_call), + StfTrustedOperation::indirect_call(signed_call) => + Self::validate_trusted_call(signed_call), + StfTrustedOperation::get(getter) => match getter { + Getter::public(_) => + return Box::pin(ready(Ok(Err(TransactionValidityError::Unknown( + UnknownTransaction::CannotLookup, + ))))), + Getter::trusted(trusted_getter) => ValidTransaction { + priority: 1 << 20, + requires: vec![], + provides: vec![trusted_getter.signature.encode()], + longevity: 64, + propagate: true, + }, + }, + }; + Box::pin(ready(Ok(Ok(operation)))) + } + + fn block_id_to_number( + &self, + at: &BlockId, + ) -> error::Result>> { + Ok(match at { + BlockId::Number(num) => Some(*num), + BlockId::Hash(_) => None, + }) + } + + fn block_id_to_hash( + &self, + at: &BlockId, + ) -> error::Result> { + Ok(match at { + //BlockId::Hash(x) => Some(x.clone()), + BlockId::Hash(_x) => None, + // dummy + BlockId::Number(_num) => None, + }) + } + + fn hash_and_length(&self, ex: &StfTrustedOperation) -> (ExtrinsicHash, usize) { + debug!("[Pool] creating hash of {:?}", ex); + ex.using_encoded(|x| (<::Hashing as HashT>::hash(x), x.len())) + } + + fn block_body(&self, _id: &BlockId) -> Self::BodyFuture { + ready(Ok(None)) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use futures::executor; + use ita_stf::{KeyPair, PublicGetter, TrustedCall, TrustedOperation}; + use itp_types::Block as ParentchainBlock; + use sp_core::{ed25519, Pair}; + use sp_keyring::AccountKeyring; + + type TestChainApi = SidechainApi; + + type Seed = [u8; 32]; + const TEST_SEED: Seed = *b"12345678901234567890123456789012"; + + #[test] + fn indirect_calls_are_valid() { + let chain_api = TestChainApi::default(); + let operation = create_indirect_trusted_operation(); + + let validation = executor::block_on(chain_api.validate_transaction( + TrustedOperationSource::Local, + operation, + ShardIdentifier::default(), + )) + .unwrap(); + + assert!(validation.is_ok()); + } + + #[test] + fn public_getters_are_not_valid() { + let chain_api = TestChainApi::default(); + let public_getter = TrustedOperation::get(Getter::public(PublicGetter::some_value)); + + let validation = executor::block_on(chain_api.validate_transaction( + TrustedOperationSource::Local, + public_getter, + ShardIdentifier::default(), + )) + .unwrap(); + + assert!(validation.is_err()); + } + + fn create_indirect_trusted_operation() -> TrustedOperation { + let trusted_call_signed = TrustedCall::balance_transfer( + AccountKeyring::Alice.public().into(), + AccountKeyring::Bob.public().into(), + 1000u128, + ) + .sign(&KeyPair::Ed25519(Box::new(signer())), 1, &[1u8; 32], &ShardIdentifier::default()); + TrustedOperation::indirect_call(trusted_call_signed) + } + + fn signer() -> ed25519::Pair { + ed25519::Pair::from_seed(&TEST_SEED) + } +} diff --git a/tee-worker/core-primitives/top-pool-author/src/author.rs b/tee-worker/core-primitives/top-pool-author/src/author.rs new file mode 100644 index 0000000000..bbd8aeec09 --- /dev/null +++ b/tee-worker/core-primitives/top-pool-author/src/author.rs @@ -0,0 +1,351 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +#[cfg(all(not(feature = "std"), feature = "sgx"))] +use crate::sgx_reexport_prelude::*; + +use crate::{ + client_error::Error as ClientError, + error::{Error as StateRpcError, Result}, + top_filter::Filter, + traits::{AuthorApi, OnBlockImported}, +}; +use codec::{Decode, Encode}; +use ita_stf::{hash, Getter, TrustedOperation}; +use itp_enclave_metrics::EnclaveMetric; +use itp_ocall_api::EnclaveMetricsOCallApi; +use itp_sgx_crypto::{key_repository::AccessKey, ShieldingCryptoDecrypt}; +use itp_stf_state_handler::query_shard_state::QueryShardState; +use itp_top_pool::{ + error::{Error as PoolError, IntoPoolError}, + primitives::{ + BlockHash, InPoolOperation, PoolFuture, TrustedOperationPool, TrustedOperationSource, + TxHash, + }, +}; +use itp_types::{BlockHash as SidechainBlockHash, ShardIdentifier}; +use jsonrpc_core::{ + futures::future::{ready, TryFutureExt}, + Error as RpcError, +}; +use log::*; +use sp_runtime::generic; +use std::{boxed::Box, sync::Arc, vec::Vec}; + +/// Define type of TOP filter that is used in the Author +#[cfg(feature = "sidechain")] +pub type AuthorTopFilter = crate::top_filter::CallsOnlyFilter; +#[cfg(feature = "offchain-worker")] +pub type AuthorTopFilter = crate::top_filter::IndirectCallsOnlyFilter; +#[cfg(feature = "teeracle")] // Teeracle currently does not process any trusted operations +pub type AuthorTopFilter = crate::top_filter::DenyAllFilter; + +#[cfg(not(any(feature = "sidechain", feature = "offchain-worker", feature = "teeracle")))] +pub type AuthorTopFilter = crate::top_filter::CallsOnlyFilter; + +/// Currently we treat all RPC operations as externals. +/// +/// Possibly in the future we could allow opt-in for special treatment +/// of such operations, so that the block authors can inject +/// some unique operations via RPC and have them included in the pool. +const TX_SOURCE: TrustedOperationSource = TrustedOperationSource::External; + +/// Authoring API for RPC calls +/// +/// +pub struct Author +where + TopPool: TrustedOperationPool + Sync + Send + 'static, + TopFilter: Filter, + StateFacade: QueryShardState, + ShieldingKeyRepository: AccessKey, + ::KeyType: ShieldingCryptoDecrypt, +{ + top_pool: Arc, + top_filter: TopFilter, + state_facade: Arc, + shielding_key_repo: Arc, + ocall_api: Arc, +} + +impl + Author +where + TopPool: TrustedOperationPool + Sync + Send + 'static, + TopFilter: Filter, + StateFacade: QueryShardState, + ShieldingKeyRepository: AccessKey, + ::KeyType: ShieldingCryptoDecrypt, + OCallApi: EnclaveMetricsOCallApi + Send + Sync + 'static, +{ + /// Create new instance of Authoring API. + pub fn new( + top_pool: Arc, + top_filter: TopFilter, + state_facade: Arc, + encryption_key: Arc, + ocall_api: Arc, + ) -> Self { + Author { top_pool, top_filter, state_facade, shielding_key_repo: encryption_key, ocall_api } + } +} + +enum TopSubmissionMode { + Submit, + SubmitWatch, +} + +impl + Author +where + TopPool: TrustedOperationPool + Sync + Send + 'static, + TopFilter: Filter, + StateFacade: QueryShardState, + ShieldingKeyRepository: AccessKey, + ::KeyType: ShieldingCryptoDecrypt, + OCallApi: EnclaveMetricsOCallApi + Send + Sync + 'static, +{ + fn process_top( + &self, + ext: Vec, + shard: ShardIdentifier, + submission_mode: TopSubmissionMode, + ) -> PoolFuture, RpcError> { + // check if shard exists + match self.state_facade.shard_exists(&shard) { + Err(_) => return Box::pin(ready(Err(ClientError::InvalidShard.into()))), + Ok(shard_exists) => + if !shard_exists { + return Box::pin(ready(Err(ClientError::InvalidShard.into()))) + }, + }; + + // decrypt call + let shielding_key = match self.shielding_key_repo.retrieve_key() { + Ok(k) => k, + Err(_) => return Box::pin(ready(Err(ClientError::BadFormatDecipher.into()))), + }; + let request_vec = match shielding_key.decrypt(ext.as_slice()) { + Ok(req) => req, + Err(_) => return Box::pin(ready(Err(ClientError::BadFormatDecipher.into()))), + }; + // decode call + let trusted_operation = match TrustedOperation::decode(&mut request_vec.as_slice()) { + Ok(op) => op, + Err(_) => return Box::pin(ready(Err(ClientError::BadFormat.into()))), + }; + + // apply top filter - return error if this specific type of trusted operation + // is not allowed by the filter + if !self.top_filter.filter(&trusted_operation) { + return Box::pin(ready(Err(ClientError::UnsupportedOperation.into()))) + } + + //let best_block_hash = self.client.info().best_hash; + // dummy block hash + let best_block_hash = Default::default(); + + // Update metric + if let Err(e) = self.ocall_api.update_metric(EnclaveMetric::TopPoolSizeIncrement) { + warn!("Failed to update metric for top pool size: {:?}", e); + } + + if let Some(trusted_call_signed) = trusted_operation.to_call() { + debug!( + "Submitting trusted call to TOP pool: {:?}, TOP hash: {:?}", + trusted_call_signed.call, + self.hash_of(&trusted_operation) + ); + } else if let TrustedOperation::get(Getter::trusted(ref trusted_getter_signed)) = + trusted_operation + { + debug!( + "Submitting trusted getter to TOP pool: {:?}, TOP hash: {:?}", + trusted_getter_signed.getter, + self.hash_of(&trusted_operation) + ); + } + + match submission_mode { + TopSubmissionMode::Submit => Box::pin( + self.top_pool + .submit_one( + &generic::BlockId::hash(best_block_hash), + TX_SOURCE, + trusted_operation, + shard, + ) + .map_err(map_top_error::), + ), + + TopSubmissionMode::SubmitWatch => Box::pin( + self.top_pool + .submit_and_watch( + &generic::BlockId::hash(best_block_hash), + TX_SOURCE, + trusted_operation, + shard, + ) + .map_err(map_top_error::), + ), + } + } + + fn remove_top( + &self, + bytes_or_hash: hash::TrustedOperationOrHash>, + shard: ShardIdentifier, + inblock: bool, + ) -> Result> { + let hash = match bytes_or_hash { + hash::TrustedOperationOrHash::Hash(h) => Ok(h), + hash::TrustedOperationOrHash::OperationEncoded(bytes) => { + match Decode::decode(&mut bytes.as_slice()) { + Ok(op) => Ok(self.top_pool.hash_of(&op)), + Err(e) => { + error!("Failed to decode trusted operation: {:?}, operation will not be removed from pool", e); + Err(StateRpcError::CodecError(e)) + }, + } + }, + hash::TrustedOperationOrHash::Operation(op) => Ok(self.top_pool.hash_of(&op)), + }?; + + debug!("removing {:?} from top pool", hash); + + // Update metric + if let Err(e) = self.ocall_api.update_metric(EnclaveMetric::TopPoolSizeDecrement) { + warn!("Failed to update metric for top pool size: {:?}", e); + } + + let removed_op_hash = self + .top_pool + .remove_invalid(&[hash], shard, inblock) + // Only remove a single element, so first should return Ok(). + .first() + .map(|o| o.hash().clone()) + .ok_or(PoolError::InvalidTrustedOperation)?; + + Ok(removed_op_hash) + } +} + +fn map_top_error(error: P::Error) -> RpcError { + StateRpcError::PoolError( + error + .into_pool_error() + .map(Into::into) + .unwrap_or_else(|_error| PoolError::Verification), + ) + .into() +} + +impl + AuthorApi, BlockHash> + for Author +where + TopPool: TrustedOperationPool + Sync + Send + 'static, + TopFilter: Filter, + StateFacade: QueryShardState, + ShieldingKeyRepository: AccessKey, + ::KeyType: ShieldingCryptoDecrypt, + OCallApi: EnclaveMetricsOCallApi + Send + Sync + 'static, +{ + fn submit_top( + &self, + ext: Vec, + shard: ShardIdentifier, + ) -> PoolFuture, RpcError> { + self.process_top(ext, shard, TopSubmissionMode::Submit) + } + + /// Get hash of TrustedOperation + fn hash_of(&self, xt: &TrustedOperation) -> TxHash { + self.top_pool.hash_of(xt) + } + + fn pending_tops(&self, shard: ShardIdentifier) -> Result>> { + Ok(self.top_pool.ready(shard).map(|top| top.data().encode()).collect()) + } + + fn get_pending_trusted_getters(&self, shard: ShardIdentifier) -> Vec { + self.top_pool + .ready(shard) + .map(|o| o.data().clone()) + .into_iter() + .filter(|o| matches!(o, TrustedOperation::get(Getter::trusted(_)))) + .collect() + } + + fn get_pending_trusted_calls(&self, shard: ShardIdentifier) -> Vec { + self.top_pool + .ready(shard) + .map(|o| o.data().clone()) + .into_iter() + .filter(|o| { + matches!(o, TrustedOperation::direct_call(_)) + || matches!(o, TrustedOperation::indirect_call(_)) + }) + .collect() + } + + fn get_shards(&self) -> Vec { + self.top_pool.shards() + } + + fn remove_calls_from_pool( + &self, + shard: ShardIdentifier, + executed_calls: Vec<(hash::TrustedOperationOrHash>, bool)>, + ) -> Vec>> { + let mut failed_to_remove = Vec::new(); + for (executed_call, inblock) in executed_calls { + if let Err(e) = self.remove_top(executed_call.clone(), shard, inblock) { + // We don't want to return here before all calls have been iterated through, + // hence log message and collect failed calls in vec. + debug!("Error removing trusted call from top pool: {:?}", e); + failed_to_remove.push(executed_call); + } + } + failed_to_remove + } + + fn watch_top( + &self, + ext: Vec, + shard: ShardIdentifier, + ) -> PoolFuture, RpcError> { + self.process_top(ext, shard, TopSubmissionMode::SubmitWatch) + } +} + +impl OnBlockImported + for Author +where + TopPool: TrustedOperationPool + Sync + Send + 'static, + TopFilter: Filter, + StateFacade: QueryShardState, + ShieldingKeyRepository: AccessKey, + ::KeyType: ShieldingCryptoDecrypt, + OCallApi: EnclaveMetricsOCallApi + Send + Sync + 'static, +{ + type Hash = ::Hash; + + fn on_block_imported(&self, hashes: &[Self::Hash], block_hash: SidechainBlockHash) { + self.top_pool.on_block_imported(hashes, block_hash) + } +} diff --git a/tee-worker/core-primitives/top-pool-author/src/author_tests.rs b/tee-worker/core-primitives/top-pool-author/src/author_tests.rs new file mode 100644 index 0000000000..9a6b76c4fb --- /dev/null +++ b/tee-worker/core-primitives/top-pool-author/src/author_tests.rs @@ -0,0 +1,151 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +use crate::{ + author::Author, + test_fixtures::{ + create_indirect_trusted_operation, shard_id, trusted_call_signed, trusted_getter_signed, + }, + test_utils::submit_operation_to_top_pool, + top_filter::{AllowAllTopsFilter, Filter, GettersOnlyFilter}, + traits::AuthorApi, +}; +use codec::{Decode, Encode}; +use ita_stf::TrustedOperation; +use itp_sgx_crypto::{mocks::KeyRepositoryMock, ShieldingCryptoDecrypt, ShieldingCryptoEncrypt}; +use itp_stf_state_handler::handle_state::HandleState; +use itp_test::mock::{ + handle_state_mock::HandleStateMock, metrics_ocall_mock::MetricsOCallMock, + shielding_crypto_mock::ShieldingCryptoMock, +}; +use itp_top_pool::mocks::trusted_operation_pool_mock::TrustedOperationPoolMock; +use sgx_crypto_helper::{rsa3072::Rsa3072KeyPair, RsaKeyPair}; +use sp_core::H256; +use std::sync::Arc; + +type TestAuthor = Author< + TrustedOperationPoolMock, + Filter, + HandleStateMock, + KeyRepositoryMock, + MetricsOCallMock, +>; + +#[test] +fn top_encryption_works() { + let trusted_call = TrustedOperation::from(trusted_call_signed()); + let trusted_getter = TrustedOperation::from(trusted_getter_signed()); + + assert_eq!(trusted_call, encrypt_and_decrypt_top(&trusted_call)); + assert_eq!(trusted_getter, encrypt_and_decrypt_top(&trusted_getter)); +} + +fn encrypt_and_decrypt_top(top: &TrustedOperation) -> TrustedOperation { + let encryption_key = Rsa3072KeyPair::new().unwrap(); + let encrypted_top = encryption_key.encrypt(top.encode().as_slice()).unwrap(); + let decrypted_top = encryption_key.decrypt(encrypted_top.as_slice()).unwrap(); + + TrustedOperation::decode(&mut decrypted_top.as_slice()).unwrap() +} + +#[test] +fn submitting_to_author_inserts_in_pool() { + let (author, top_pool, shielding_key) = create_author_with_filter(AllowAllTopsFilter); + let top = TrustedOperation::from(trusted_getter_signed()); + + let submit_response: H256 = + submit_operation_to_top_pool(&author, &top, &shielding_key, shard_id()).unwrap(); + + assert!(!submit_response.is_zero()); + + let submitted_transactions = top_pool.get_last_submitted_transactions(); + assert_eq!(1, submitted_transactions.len()); +} + +#[test] +fn submitting_call_to_author_when_top_is_filtered_returns_error() { + let (author, top_pool, shielding_key) = create_author_with_filter(GettersOnlyFilter); + let top = TrustedOperation::direct_call(trusted_call_signed()); + + let submit_response = submit_operation_to_top_pool(&author, &top, &shielding_key, shard_id()); + + assert!(submit_response.is_err()); + assert!(top_pool.get_last_submitted_transactions().is_empty()); +} + +#[test] +fn submitting_getter_to_author_when_top_is_filtered_inserts_in_pool() { + let (author, top_pool, shielding_key) = create_author_with_filter(GettersOnlyFilter); + let top = TrustedOperation::from(trusted_getter_signed()); + + let submit_response = + submit_operation_to_top_pool(&author, &top, &shielding_key, shard_id()).unwrap(); + + assert!(!submit_response.is_zero()); + assert_eq!(1, top_pool.get_last_submitted_transactions().len()); +} + +#[test] +fn submitting_direct_call_works() { + let trusted_operation = TrustedOperation::direct_call(trusted_call_signed()); + let (author, top_pool, shielding_key) = create_author_with_filter(AllowAllTopsFilter); + + let _ = submit_operation_to_top_pool(&author, &trusted_operation, &shielding_key, shard_id()) + .unwrap(); + + assert_eq!(1, top_pool.get_last_submitted_transactions().len()); + assert_eq!(1, author.get_pending_trusted_calls(shard_id()).len()); +} + +#[test] +fn submitting_indirect_call_works() { + let (author, top_pool, shielding_key) = create_author_with_filter(AllowAllTopsFilter); + let trusted_operation = create_indirect_trusted_operation(); + + let _ = submit_operation_to_top_pool(&author, &trusted_operation, &shielding_key, shard_id()) + .unwrap(); + + assert_eq!(1, top_pool.get_last_submitted_transactions().len()); + assert_eq!(1, author.get_pending_trusted_calls(shard_id()).len()); +} + +fn create_author_with_filter>( + filter: F, +) -> (TestAuthor, Arc, ShieldingCryptoMock) { + let top_pool = Arc::new(TrustedOperationPoolMock::default()); + + let shard_id = shard_id(); + let state_facade = HandleStateMock::from_shard(shard_id).unwrap(); + let _ = state_facade.load(&shard_id).unwrap(); + + let encryption_key = ShieldingCryptoMock::default(); + let shielding_key_repo = + Arc::new(KeyRepositoryMock::::new(encryption_key.clone())); + let ocall_mock = Arc::new(MetricsOCallMock::default()); + + ( + Author::new( + top_pool.clone(), + filter, + Arc::new(state_facade), + shielding_key_repo, + ocall_mock, + ), + top_pool, + encryption_key, + ) +} diff --git a/tee-worker/core-primitives/top-pool-author/src/client_error.rs b/tee-worker/core-primitives/top-pool-author/src/client_error.rs new file mode 100644 index 0000000000..badd278008 --- /dev/null +++ b/tee-worker/core-primitives/top-pool-author/src/client_error.rs @@ -0,0 +1,183 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +//! Authoring RPC module client errors. + +#[cfg(all(not(feature = "std"), feature = "sgx"))] +use crate::sgx_reexport_prelude::*; + +use derive_more::{Display, From}; +use jsonrpc_core as rpc_core; +use std::{boxed::Box, format}; + +/// Author RPC Result type. +pub type Result = core::result::Result; + +/// Author RPC errors. +#[derive(Debug, Display, From)] +pub enum Error { + /// Client error. + #[display(fmt = "Client error: {}", _0)] + #[from(ignore)] + Client(Box), + /// TrustedOperation pool error, + #[display(fmt = "TrustedOperation pool error: {}", _0)] + Pool(itp_top_pool::error::Error), + /// Verification error + #[display(fmt = "Extrinsic verification error")] + #[from(ignore)] + Verification, + /// Incorrect extrinsic format. + #[display(fmt = "Invalid trusted call format")] + BadFormat, + // Incorrect enciphered trusted call format. + #[display(fmt = "Invalid enciphered trusted call format")] + BadFormatDecipher, + /// Incorrect seed phrase. + #[display(fmt = "Invalid seed phrase/SURI")] + BadSeedPhrase, + /// Key type ID has an unknown format. + #[display(fmt = "Invalid key type ID format (should be of length four)")] + BadKeyType, + /// Key type ID has some unsupported crypto. + #[display(fmt = "The crypto of key type ID is unknown")] + UnsupportedKeyType, + /// Some random issue with the key store. Shouldn't happen. + #[display(fmt = "The key store is unavailable")] + KeyStoreUnavailable, + /// Invalid session keys encoding. + #[display(fmt = "Session keys are not encoded correctly")] + InvalidSessionKeys, + /// Shard does not exist. + #[display(fmt = "Shard does not exist")] + InvalidShard, + /// Unsupported trusted operation (in case we allow only certain types of operations, using filters) + #[display(fmt = "Unsupported operation type")] + UnsupportedOperation, +} + +impl std::error::Error for Error { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + match self { + Error::Client(ref err) => Some(&**err), + //Error::Pool(ref err) => Some(err), + //Error::Verification(ref err) => Some(&**err), + _ => None, + } + } +} + +/// Base code for all authorship errors. +const BASE_ERROR: i64 = 1000; +/// Extrinsic has an invalid format. +const BAD_FORMAT: i64 = BASE_ERROR + 1; +/// Error during operation verification in runtime. +const VERIFICATION_ERROR: i64 = BASE_ERROR + 2; + +/// Pool rejected the operation as invalid +const POOL_INVALID_TX: i64 = BASE_ERROR + 10; +/// Cannot determine operation validity. +const POOL_UNKNOWN_VALIDITY: i64 = POOL_INVALID_TX + 1; +/// The operation is temporarily banned. +const POOL_TEMPORARILY_BANNED: i64 = POOL_INVALID_TX + 2; +/// The operation is already in the pool +const POOL_ALREADY_IMPORTED: i64 = POOL_INVALID_TX + 3; +/// TrustedOperation has too low priority to replace existing one in the pool. +const POOL_TOO_LOW_PRIORITY: i64 = POOL_INVALID_TX + 4; +/// Including this operation would cause a dependency cycle. +const POOL_CYCLE_DETECTED: i64 = POOL_INVALID_TX + 5; +/// The operation was not included to the pool because of the limits. +const POOL_IMMEDIATELY_DROPPED: i64 = POOL_INVALID_TX + 6; +/// The key type crypto is not known. +const UNSUPPORTED_KEY_TYPE: i64 = POOL_INVALID_TX + 7; + +impl From for rpc_core::Error { + fn from(e: Error) -> Self { + use itp_top_pool::error::Error as PoolError; + + match e { + Error::BadFormat => rpc_core::Error { + code: rpc_core::ErrorCode::ServerError(BAD_FORMAT), + message: "Trusted operation has invalid format".into(), + data: None, + }, + Error::BadFormatDecipher => rpc_core::Error { + code: rpc_core::ErrorCode::ServerError(BAD_FORMAT), + message: "Trusted operation could not be deciphered".into(), + data: None, + }, + Error::Verification => rpc_core::Error { + code: rpc_core::ErrorCode::ServerError(VERIFICATION_ERROR), + message: "Verification Error".into(), + data: Some(format!("{:?}", e).into()), + }, + Error::InvalidShard => rpc_core::Error { + code: rpc_core::ErrorCode::ServerError(VERIFICATION_ERROR), + message: "Shard does not exist".into(), + data: Some(format!("{:?}", e).into()), + }, + Error::Pool(PoolError::InvalidTrustedOperation) => rpc_core::Error { + code: rpc_core::ErrorCode::ServerError(POOL_INVALID_TX), + message: "Invalid Trusted Operation".into(), + data: None, + }, + Error::Pool(PoolError::UnknownTrustedOperation) => rpc_core::Error { + code: rpc_core::ErrorCode::ServerError(POOL_UNKNOWN_VALIDITY), + message: "Unknown Trusted Operation Validity".into(), + data: None, + }, + Error::Pool(PoolError::TemporarilyBanned) => rpc_core::Error { + code: rpc_core::ErrorCode::ServerError(POOL_TEMPORARILY_BANNED), + message: "Trusted Operation is temporarily banned".into(), + data: None, + }, + Error::Pool(PoolError::AlreadyImported) => rpc_core::Error { + code: rpc_core::ErrorCode::ServerError(POOL_ALREADY_IMPORTED), + message: "Trusted Operation Already Imported".into(), + data: None, + }, + Error::Pool(PoolError::TooLowPriority(new)) => rpc_core::Error { + code: rpc_core::ErrorCode::ServerError(POOL_TOO_LOW_PRIORITY), + message: format!("Priority is too low: {}", new), + data: Some("The Trusted Operation has too low priority to replace another Trusted Operation already in the pool.".into()), + }, + Error::Pool(PoolError::CycleDetected) => rpc_core::Error { + code: rpc_core::ErrorCode::ServerError(POOL_CYCLE_DETECTED), + message: "Cycle Detected".into(), + data: None, + }, + Error::Pool(PoolError::ImmediatelyDropped) => rpc_core::Error { + code: rpc_core::ErrorCode::ServerError(POOL_IMMEDIATELY_DROPPED), + message: "Immediately Dropped".into(), + data: Some("The Trusted Operation couldn't enter the pool because of the limit".into()), + }, + Error::UnsupportedKeyType => rpc_core::Error { + code: rpc_core::ErrorCode::ServerError(UNSUPPORTED_KEY_TYPE), + message: "Unknown key type crypto" .into(), + data: Some( + "The crypto for the given key type is unknown, please add the public key to the \ + request to insert the key successfully.".into() + ), + }, + e => rpc_core::Error { + code: rpc_core::ErrorCode::InternalError, + message: "Unknown error occurred".into(), + data: Some(format!("{:?}", e).into()), + }, + } + } +} diff --git a/tee-worker/core-primitives/top-pool-author/src/error.rs b/tee-worker/core-primitives/top-pool-author/src/error.rs new file mode 100644 index 0000000000..1c967a1b82 --- /dev/null +++ b/tee-worker/core-primitives/top-pool-author/src/error.rs @@ -0,0 +1,111 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +#[cfg(all(not(feature = "std"), feature = "sgx"))] +use crate::sgx_reexport_prelude::*; + +use crate::client_error::Error as ClientError; +use core::pin::Pin; +use derive_more::{Display, From}; +use itp_top_pool::error::{Error as PoolError, IntoPoolError}; +use jsonrpc_core as rpc; +use std::{boxed::Box, error, format, string::String}; + +/// State RPC Result type. +pub type Result = core::result::Result; + +/// State RPC future Result type. +pub type FutureResult = + Pin> + Send>>; + +/// State RPC errors. +#[derive(Debug, Display, From)] +pub enum Error { + /// Client error. + #[display(fmt = "Client error: {}", _0)] + Client(Box), + /// Provided block range couldn't be resolved to a list of blocks. + #[display(fmt = "Cannot resolve a block range ['{:?}' ... '{:?}]. {}", from, to, details)] + InvalidBlockRange { + /// Beginning of the block range. + from: String, + /// End of the block range. + to: String, + /// Details of the error message. + details: String, + }, + /// Provided count exceeds maximum value. + #[display(fmt = "count exceeds maximum value. value: {}, max: {}", value, max)] + InvalidCount { + /// Provided value + value: u32, + /// Maximum allowed value + max: u32, + }, + + /// Wrapping of PoolError to RPC Error + PoolError(PoolError), + + /// Wrapping of ClientError to RPC Error + ClientError(ClientError), + + #[display(fmt = "Codec error: {}", _0)] + CodecError(codec::Error), +} + +impl error::Error for Error { + fn source(&self) -> Option<&(dyn error::Error + 'static)> { + match self { + Error::Client(ref err) => Some(&**err), + _ => None, + } + } +} + +impl IntoPoolError for Error { + fn into_pool_error(self) -> std::result::Result { + match self { + Error::PoolError(e) => Ok(e), + e => Err(e), + } + } +} + +/// Base code for all state errors. +const BASE_ERROR: i64 = 4000; + +impl From for rpc::Error { + fn from(e: Error) -> Self { + match e { + Error::InvalidBlockRange { .. } => rpc::Error { + code: rpc::ErrorCode::ServerError(BASE_ERROR + 1), + message: format!("{}", e), + data: None, + }, + Error::InvalidCount { .. } => rpc::Error { + code: rpc::ErrorCode::ServerError(BASE_ERROR + 2), + message: format!("{}", e), + data: None, + }, + e => rpc::Error { + code: rpc::ErrorCode::ServerError(BASE_ERROR + 4), + message: format!("{}", e), + data: None, + }, + } + } +} diff --git a/tee-worker/core-primitives/top-pool-author/src/lib.rs b/tee-worker/core-primitives/top-pool-author/src/lib.rs new file mode 100644 index 0000000000..bc523e9cf8 --- /dev/null +++ b/tee-worker/core-primitives/top-pool-author/src/lib.rs @@ -0,0 +1,52 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +#![feature(trait_alias)] +#![cfg_attr(feature = "mocks", feature(drain_filter))] +#![cfg_attr(not(feature = "std"), no_std)] + +#[cfg(all(feature = "std", feature = "sgx"))] +compile_error!("feature \"std\" and feature \"sgx\" cannot be enabled at the same time"); + +#[cfg(all(not(feature = "std"), feature = "sgx"))] +extern crate sgx_tstd as std; + +// re-export module to properly feature gate sgx and regular std environment +#[cfg(all(not(feature = "std"), feature = "sgx"))] +pub mod sgx_reexport_prelude { + pub use jsonrpc_core_sgx as jsonrpc_core; + pub use thiserror_sgx as thiserror; +} + +pub mod api; +pub mod author; +pub mod client_error; +pub mod error; +pub mod top_filter; +pub mod traits; + +#[cfg(test)] +mod author_tests; + +#[cfg(test)] +mod test_fixtures; + +#[cfg(any(test, feature = "test"))] +pub mod test_utils; + +#[cfg(feature = "mocks")] +pub mod mocks; diff --git a/tee-worker/core-primitives/top-pool-author/src/mocks.rs b/tee-worker/core-primitives/top-pool-author/src/mocks.rs new file mode 100644 index 0000000000..38c6d58ef8 --- /dev/null +++ b/tee-worker/core-primitives/top-pool-author/src/mocks.rs @@ -0,0 +1,202 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +#[cfg(all(not(feature = "std"), feature = "sgx"))] +use crate::sgx_reexport_prelude::*; + +#[cfg(feature = "sgx")] +use std::sync::SgxRwLock as RwLock; + +#[cfg(feature = "std")] +use std::sync::RwLock; + +use crate::{ + error::Result, + traits::{AuthorApi, OnBlockImported}, +}; +use codec::Decode; +use ita_stf::{ + hash::{Hash, TrustedOperationOrHash}, + Getter, TrustedGetterSigned, TrustedOperation, +}; +use itp_top_pool::primitives::PoolFuture; +use itp_types::ShardIdentifier; +use jsonrpc_core::{futures::future::ready, Error as RpcError}; +use sp_core::{blake2_256, H256}; +use std::{boxed::Box, collections::HashMap, marker::PhantomData, vec, vec::Vec}; + +#[derive(Default)] +pub struct AuthorApiMock { + tops: RwLock>>>, + _phantom: PhantomData<(Hash, BlockHash)>, + pub remove_attempts: RwLock, +} + +impl AuthorApiMock { + fn decode_trusted_operation(mut encoded_operation: &[u8]) -> Option { + TrustedOperation::decode(&mut encoded_operation).ok() + } + + fn decode_trusted_getter_signed(mut encoded_operation: &[u8]) -> Option { + TrustedGetterSigned::decode(&mut encoded_operation).ok() + } + + fn remove_top( + &self, + bytes_or_hash: Vec>, + shard: ShardIdentifier, + _inblock: bool, + ) -> Result> { + let hashes = bytes_or_hash + .into_iter() + .map(|x| match x { + TrustedOperationOrHash::Hash(h) => h, + TrustedOperationOrHash::OperationEncoded(bytes) => { + let top: TrustedOperation = + TrustedOperation::decode(&mut bytes.as_slice()).unwrap(); + top.hash() + }, + TrustedOperationOrHash::Operation(op) => op.hash(), + }) + .collect::>(); + + let mut tops_lock = self.tops.write().unwrap(); + + match tops_lock.get_mut(&shard) { + Some(tops_encoded) => { + let removed_tops = tops_encoded + .drain_filter(|t| hashes.contains(&blake2_256(t).into())) + .map(|t| blake2_256(&t).into()) + .collect::>(); + Ok(removed_tops) + }, + None => Ok(Vec::new()), + } + } +} + +impl AuthorApi for AuthorApiMock { + fn submit_top(&self, extrinsic: Vec, shard: ShardIdentifier) -> PoolFuture { + let mut write_lock = self.tops.write().unwrap(); + let extrinsics = write_lock.entry(shard).or_default(); + extrinsics.push(extrinsic); + Box::pin(ready(Ok(H256::default()))) + } + + fn hash_of(&self, xt: &TrustedOperation) -> H256 { + xt.hash() + } + + fn pending_tops(&self, shard: ShardIdentifier) -> Result>> { + let extrinsics = self.tops.read().unwrap().get(&shard).cloned(); + Ok(extrinsics.unwrap_or_default()) + } + + fn get_pending_trusted_getters(&self, shard: ShardIdentifier) -> Vec { + self.tops + .read() + .unwrap() + .get(&shard) + .map(|encoded_operations| { + let mut trusted_getters: Vec = Vec::new(); + for encoded_operation in encoded_operations { + if let Some(g) = Self::decode_trusted_getter_signed(encoded_operation) { + trusted_getters.push(TrustedOperation::get(Getter::trusted(g))); + } + } + trusted_getters + }) + .unwrap_or_default() + } + + fn get_pending_trusted_calls(&self, shard: ShardIdentifier) -> Vec { + self.tops + .read() + .unwrap() + .get(&shard) + .map(|encoded_operations| { + let mut trusted_operations: Vec = Vec::new(); + for encoded_operation in encoded_operations { + if let Some(o) = Self::decode_trusted_operation(encoded_operation) { + trusted_operations.push(o); + } + } + trusted_operations + }) + .unwrap_or_default() + } + + fn get_shards(&self) -> Vec { + self.tops.read().unwrap().keys().cloned().collect() + } + + fn remove_calls_from_pool( + &self, + shard: ShardIdentifier, + executed_calls: Vec<(TrustedOperationOrHash, bool)>, + ) -> Vec> { + let mut remove_attempts_lock = self.remove_attempts.write().unwrap(); + *remove_attempts_lock += 1; + + let mut failed_to_remove = Vec::new(); + for (executed_call, inblock) in executed_calls { + if self.remove_top(vec![executed_call.clone()], shard, inblock).is_err() { + failed_to_remove.push(executed_call); + } + } + failed_to_remove + } + + fn watch_top(&self, _ext: Vec, _shard: ShardIdentifier) -> PoolFuture { + todo!() + } +} + +impl OnBlockImported for AuthorApiMock { + type Hash = H256; + + fn on_block_imported(&self, _hashes: &[Self::Hash], _block_hash: H256) {} +} + +#[cfg(test)] +mod tests { + + use super::*; + use crate::test_fixtures::{create_indirect_trusted_operation, shard_id}; + use codec::Encode; + use futures::executor::block_on; + use std::vec; + + #[test] + fn submitted_tops_can_be_removed_again() { + let author = AuthorApiMock::::default(); + let shard = shard_id(); + let trusted_operation = create_indirect_trusted_operation(); + + let _ = block_on(author.submit_top(trusted_operation.encode(), shard)).unwrap(); + + assert_eq!(1, author.pending_tops(shard).unwrap().len()); + assert_eq!(1, author.get_pending_trusted_calls(shard).len()); + assert_eq!(0, author.get_pending_trusted_getters(shard).len()); + + let trusted_operation_or_hash = TrustedOperationOrHash::from_top(trusted_operation.clone()); + let removed_tops = author.remove_top(vec![trusted_operation_or_hash], shard, true).unwrap(); + + assert_eq!(1, removed_tops.len()); + assert!(author.tops.read().unwrap().get(&shard).unwrap().is_empty()); + } +} diff --git a/tee-worker/core-primitives/top-pool-author/src/test_fixtures.rs b/tee-worker/core-primitives/top-pool-author/src/test_fixtures.rs new file mode 100644 index 0000000000..cacff6df0c --- /dev/null +++ b/tee-worker/core-primitives/top-pool-author/src/test_fixtures.rs @@ -0,0 +1,68 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +use codec::Encode; +use ita_stf::{ + Getter, KeyPair, ShardIdentifier, TrustedCall, TrustedCallSigned, TrustedGetter, + TrustedOperation, +}; +use sp_core::{ed25519, Pair}; +use sp_runtime::traits::{BlakeTwo256, Hash}; +use std::{boxed::Box, vec}; + +type Seed = [u8; 32]; +const TEST_SEED: Seed = *b"12345678901234567890123456789012"; + +pub(crate) fn trusted_call_signed() -> TrustedCallSigned { + let account = ed25519::Pair::from_seed(&TEST_SEED); + let call = + TrustedCall::balance_shield(account.public().into(), account.public().into(), 12u128); + call.sign(&KeyPair::Ed25519(Box::new(account)), 0, &mr_enclave(), &shard_id()) +} + +pub(crate) fn trusted_getter_signed() -> Getter { + let account = ed25519::Pair::from_seed(&TEST_SEED); + let getter = TrustedGetter::free_balance(account.public().into()); + Getter::trusted(getter.sign(&KeyPair::Ed25519(Box::new(account)))) +} + +pub(crate) fn create_indirect_trusted_operation() -> TrustedOperation { + let account = ed25519::Pair::from_seed(&TEST_SEED); + let trusted_call_signed = TrustedCall::balance_transfer( + alice_pair().public().into(), + bob_pair().public().into(), + 1000u128, + ) + .sign(&KeyPair::Ed25519(Box::new(account)), 1, &mr_enclave(), &shard_id()); + TrustedOperation::indirect_call(trusted_call_signed) +} + +pub(crate) fn mr_enclave() -> [u8; 32] { + [1u8; 32] +} + +pub(crate) fn shard_id() -> ShardIdentifier { + BlakeTwo256::hash(vec![1u8, 2u8, 3u8].as_slice().encode().as_slice()) +} + +fn alice_pair() -> ed25519::Pair { + ed25519::Pair::from_seed(b"22222678901234567890123456789012") +} + +fn bob_pair() -> ed25519::Pair { + ed25519::Pair::from_seed(b"33333378901234567890123456789012") +} diff --git a/tee-worker/core-primitives/top-pool-author/src/test_utils.rs b/tee-worker/core-primitives/top-pool-author/src/test_utils.rs new file mode 100644 index 0000000000..b6ef5a19f2 --- /dev/null +++ b/tee-worker/core-primitives/top-pool-author/src/test_utils.rs @@ -0,0 +1,44 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +#[cfg(all(not(feature = "std"), feature = "sgx"))] +use crate::sgx_reexport_prelude::*; + +use crate::traits::AuthorApi; +use codec::Encode; +use ita_stf::{ShardIdentifier, TrustedOperation}; +use itp_sgx_crypto::ShieldingCryptoEncrypt; +use jsonrpc_core::futures::executor; +use sp_core::H256; +use std::fmt::Debug; + +/// Test utility function to submit a trusted operation on an RPC author +pub fn submit_operation_to_top_pool( + author: &R, + top: &TrustedOperation, + shielding_key: &S, + shard: ShardIdentifier, +) -> Result +where + R: AuthorApi, + S: ShieldingCryptoEncrypt, + S::Error: Debug, +{ + let top_encrypted = shielding_key.encrypt(&top.encode()).unwrap(); + let submit_future = async { author.watch_top(top_encrypted, shard).await }; + executor::block_on(submit_future) +} diff --git a/tee-worker/core-primitives/top-pool-author/src/top_filter.rs b/tee-worker/core-primitives/top-pool-author/src/top_filter.rs new file mode 100644 index 0000000000..d432df2d4e --- /dev/null +++ b/tee-worker/core-primitives/top-pool-author/src/top_filter.rs @@ -0,0 +1,212 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +use ita_stf::TrustedOperation; + +/// Trait for filtering values +/// +/// Returns `Some` if a value should be included and `None` if discarded +pub trait Filter { + type Value; + + fn filter(&self, value: &Self::Value) -> bool; +} + +/// Filter for calls only (no getters). +pub struct CallsOnlyFilter; + +impl Filter for CallsOnlyFilter { + type Value = TrustedOperation; + + fn filter(&self, value: &Self::Value) -> bool { + matches!(value, TrustedOperation::direct_call(_)) + || matches!(value, TrustedOperation::indirect_call(_)) + } +} + +/// Filter that allows all TOPs (i.e. not filter at all) +pub struct AllowAllTopsFilter; + +impl Filter for AllowAllTopsFilter { + type Value = TrustedOperation; + + fn filter(&self, _value: &Self::Value) -> bool { + true + } +} + +/// Filter that allows only trusted getters +pub struct GettersOnlyFilter; + +impl Filter for GettersOnlyFilter { + type Value = TrustedOperation; + + fn filter(&self, value: &Self::Value) -> bool { + matches!(value, TrustedOperation::get(_)) + } +} + +/// Filter for indirect calls only (no getters, no direct calls). +pub struct IndirectCallsOnlyFilter; + +impl Filter for IndirectCallsOnlyFilter { + type Value = TrustedOperation; + + fn filter(&self, value: &Self::Value) -> bool { + matches!(value, TrustedOperation::indirect_call(_)) + } +} + +/// Filter that allows no direct calls, only indirect and getters. +pub struct NoDirectCallsFilter; + +impl Filter for NoDirectCallsFilter { + type Value = TrustedOperation; + + fn filter(&self, value: &Self::Value) -> bool { + !matches!(value, TrustedOperation::direct_call(_)) + } +} + +/// Filter to deny all trusted operations. +pub struct DenyAllFilter; + +impl Filter for DenyAllFilter { + type Value = TrustedOperation; + + fn filter(&self, _value: &Self::Value) -> bool { + false + } +} + +#[cfg(test)] +mod tests { + + use super::*; + use codec::Encode; + use ita_stf::{Getter, KeyPair, TrustedCall, TrustedCallSigned, TrustedGetter}; + use itp_types::ShardIdentifier; + use sp_core::{ed25519, Pair}; + use sp_runtime::traits::{BlakeTwo256, Hash}; + use std::{ + boxed::Box, + string::{String, ToString}, + }; + + type Seed = [u8; 32]; + const TEST_SEED: Seed = *b"12345678901234567890123456789012"; + + #[test] + fn filter_returns_none_if_values_is_filtered_out() { + struct WorldFilter; + impl Filter for WorldFilter { + type Value = String; + + fn filter(&self, value: &Self::Value) -> bool { + if value.eq(&String::from("world")) { + return true + } + false + } + } + + let filter = WorldFilter; + + assert!(!filter.filter(&"hello".to_string())); + assert!(filter.filter(&"world".to_string())); + } + + #[test] + fn allow_all_tops_filter_works() { + let filter = AllowAllTopsFilter; + + assert!(filter.filter(&trusted_getter())); + assert!(filter.filter(&trusted_indirect_call())); + assert!(filter.filter(&trusted_direct_call())); + } + + #[test] + fn getters_only_filter_works() { + let filter = GettersOnlyFilter; + + assert!(filter.filter(&trusted_getter())); + assert!(!filter.filter(&trusted_indirect_call())); + assert!(!filter.filter(&trusted_direct_call())); + } + + #[test] + fn no_direct_calls_filter_works() { + let filter = NoDirectCallsFilter; + + assert!(!filter.filter(&trusted_direct_call())); + assert!(filter.filter(&trusted_indirect_call())); + assert!(filter.filter(&trusted_getter())); + } + + #[test] + fn indirect_calls_only_filter_works() { + let filter = IndirectCallsOnlyFilter; + + assert!(!filter.filter(&trusted_direct_call())); + assert!(filter.filter(&trusted_indirect_call())); + assert!(!filter.filter(&trusted_getter())); + } + + #[test] + fn calls_only_filter_works() { + let filter = CallsOnlyFilter; + + assert!(filter.filter(&trusted_direct_call())); + assert!(filter.filter(&trusted_indirect_call())); + assert!(!filter.filter(&trusted_getter())); + } + + fn trusted_direct_call() -> TrustedOperation { + TrustedOperation::direct_call(trusted_call_signed()) + } + + fn trusted_indirect_call() -> TrustedOperation { + TrustedOperation::indirect_call(trusted_call_signed()) + } + + fn trusted_getter() -> TrustedOperation { + let account = test_account(); + let getter = TrustedGetter::free_balance(account.public().into()); + let trusted_getter_signed = + Getter::trusted(getter.sign(&KeyPair::Ed25519(Box::new(account)))); + TrustedOperation::from(trusted_getter_signed) + } + + fn trusted_call_signed() -> TrustedCallSigned { + let account = test_account(); + let call = + TrustedCall::balance_shield(account.public().into(), account.public().into(), 12u128); + call.sign(&KeyPair::Ed25519(Box::new(account)), 0, &mr_enclave(), &shard_id()) + } + + fn test_account() -> ed25519::Pair { + ed25519::Pair::from_seed(&TEST_SEED) + } + + fn shard_id() -> ShardIdentifier { + BlakeTwo256::hash(vec![1u8, 2u8, 3u8].as_slice().encode().as_slice()) + } + + fn mr_enclave() -> [u8; 32] { + [1u8; 32] + } +} diff --git a/tee-worker/core-primitives/top-pool-author/src/traits.rs b/tee-worker/core-primitives/top-pool-author/src/traits.rs new file mode 100644 index 0000000000..4951d1c897 --- /dev/null +++ b/tee-worker/core-primitives/top-pool-author/src/traits.rs @@ -0,0 +1,70 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +#[cfg(all(not(feature = "std"), feature = "sgx"))] +use crate::sgx_reexport_prelude::*; + +use crate::error::Result; +use ita_stf::{hash, TrustedOperation}; +use itp_top_pool::primitives::PoolFuture; +use itp_types::{BlockHash as SidechainBlockHash, ShardIdentifier, H256}; +use jsonrpc_core::Error as RpcError; +use std::vec::Vec; + +/// Trait alias for a full STF author API +pub trait FullAuthor = AuthorApi + OnBlockImported + Send + Sync + 'static; + +/// Authoring RPC API +pub trait AuthorApi { + /// Submit encoded extrinsic for inclusion in block. + fn submit_top(&self, extrinsic: Vec, shard: ShardIdentifier) -> PoolFuture; + + /// Return hash of Trusted Operation + fn hash_of(&self, xt: &TrustedOperation) -> Hash; + + /// Returns all pending operations, potentially grouped by sender. + fn pending_tops(&self, shard: ShardIdentifier) -> Result>>; + + /// Returns all pending trusted getters. + fn get_pending_trusted_getters(&self, shard: ShardIdentifier) -> Vec; + + /// Returns all pending trusted calls. + fn get_pending_trusted_calls(&self, shard: ShardIdentifier) -> Vec; + + fn get_shards(&self) -> Vec; + + /// Remove a collection of trusted operations from the pool. + /// Return operations that were not successfully removed. + fn remove_calls_from_pool( + &self, + shard: ShardIdentifier, + executed_calls: Vec<(hash::TrustedOperationOrHash, bool)>, + ) -> Vec>; + + /// Submit an extrinsic to watch. + /// + /// See [`TrustedOperationStatus`](sp_transaction_pool::TrustedOperationStatus) for details on transaction + /// life cycle. + fn watch_top(&self, ext: Vec, shard: ShardIdentifier) -> PoolFuture; +} + +/// Trait to notify listeners/observer of a newly created block +pub trait OnBlockImported { + type Hash; + + fn on_block_imported(&self, hashes: &[Self::Hash], block_hash: SidechainBlockHash); +} diff --git a/tee-worker/core-primitives/top-pool/Cargo.toml b/tee-worker/core-primitives/top-pool/Cargo.toml new file mode 100644 index 0000000000..92d1f639ad --- /dev/null +++ b/tee-worker/core-primitives/top-pool/Cargo.toml @@ -0,0 +1,67 @@ +[package] +authors = ["Integritee AG "] +edition = "2021" +name = "itp-top-pool" +version = "0.9.0" + +[dependencies] +# sgx dependencies +sgx_tstd = { branch = "master", git = "https://github.com/apache/teaclave-sgx-sdk.git", optional = true, features = ["net", "thread", "untrusted_time"] } +sgx_types = { branch = "master", git = "https://github.com/apache/teaclave-sgx-sdk.git", optional = true } + +# local dependencies +ita-stf = { path = "../../app-libs/stf", default-features = false } +itc-direct-rpc-server = { path = "../../core/direct-rpc-server", default-features = false } +itp-types = { path = "../types", default-features = false } +its-primitives = { path = "../../sidechain/primitives", default-features = false } + +# sgx enabled external libraries +jsonrpc-core_sgx = { package = "jsonrpc-core", git = "https://github.com/scs/jsonrpc", branch = "no_std_v18", default-features = false, optional = true } +linked-hash-map_sgx = { package = "linked-hash-map", git = "https://github.com/mesalock-linux/linked-hash-map-sgx", optional = true } +thiserror_sgx = { package = "thiserror", git = "https://github.com/mesalock-linux/thiserror-sgx", tag = "sgx_1.1.3", optional = true } + +# std compatible external libraries (make sure these versions match with the sgx-enabled ones above) +jsonrpc-core = { version = "18", optional = true } +linked-hash-map = { version = "0.5.2", optional = true } +thiserror = { version = "1.0", optional = true } + +# no-std compatible libraries +byteorder = { version = "1.4.2", default-features = false } +codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } +derive_more = { version = "0.99.5" } +log = { version = "0.4", default-features = false } +serde = { version = "1.0", default-features = false, features = ["alloc", "derive"] } +sp-application-crypto = { default-features = false, git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.29" } +sp-core = { default-features = false, features = ["full_crypto"], git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.29" } +sp-runtime = { default-features = false, git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.29" } + +# dev dependencies (for tests) +[dev-dependencies] +parity-util-mem = { version = "0.11.0", default-features = false, features = ["primitive-types"] } + +[features] +default = ["std"] +mocks = [] +sgx = [ + "sgx_tstd", + "sgx_types", + "ita-stf/sgx", + "itc-direct-rpc-server/sgx", + "jsonrpc-core_sgx", + "linked-hash-map_sgx", + "thiserror_sgx", +] +std = [ + "ita-stf/std", + "itc-direct-rpc-server/std", + "itp-types/std", + "its-primitives/std", + "jsonrpc-core", + "linked-hash-map", + "log/std", + "serde/std", + "sp-core/std", + "sp-runtime/std", + "sp-application-crypto/std", + "thiserror", +] diff --git a/tee-worker/core-primitives/top-pool/src/base_pool.rs b/tee-worker/core-primitives/top-pool/src/base_pool.rs new file mode 100644 index 0000000000..0632ab0ee7 --- /dev/null +++ b/tee-worker/core-primitives/top-pool/src/base_pool.rs @@ -0,0 +1,1382 @@ +// This file is part of Substrate. + +// Copyright (C) 2018-2021 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +//! A basic version of the dependency graph. +//! +//! For a more full-featured pool, have a look at the `pool` module. + +pub extern crate alloc; +use crate::{ + error, + future::{FutureTrustedOperations, WaitingTrustedOperations}, + primitives::{InPoolOperation, PoolStatus, TrustedOperationSource as Source}, + ready::ReadyOperations, +}; +use alloc::{fmt, sync::Arc, vec, vec::Vec}; +use core::{hash, iter}; +use ita_stf::ShardIdentifier; +use log::{debug, trace, warn}; +use sp_core::hexdisplay::HexDisplay; +use sp_runtime::{ + traits::Member, + transaction_validity::{ + TransactionLongevity as Longevity, TransactionPriority as Priority, TransactionTag as Tag, + }, +}; +use std::collections::HashSet; + +/// Successful import result. +#[derive(Debug, PartialEq, Eq)] +pub enum Imported { + /// TrustedOperation was successfully imported to Ready queue. + Ready { + /// Hash of operation that was successfully imported. + hash: Hash, + /// operations that got promoted from the Future queue. + promoted: Vec, + /// operations that failed to be promoted from the Future queue and are now discarded. + failed: Vec, + /// operations removed from the Ready pool (replaced). + removed: Vec>>, + }, + /// TrustedOperation was successfully imported to Future queue. + Future { + /// Hash of operation that was successfully imported. + hash: Hash, + }, +} + +impl Imported { + /// Returns the hash of imported operation. + pub fn hash(&self) -> &Hash { + use self::Imported::*; + match *self { + Ready { ref hash, .. } => hash, + Future { ref hash, .. } => hash, + } + } +} + +/// Status of pruning the queue. +#[derive(Debug)] +pub struct PruneStatus { + /// A list of imports that satisfying the tag triggered. + pub promoted: Vec>, + /// A list of operations that failed to be promoted and now are discarded. + pub failed: Vec, + /// A list of operations that got pruned from the ready queue. + pub pruned: Vec>>, +} + +/// Immutable operation +#[derive(PartialEq, Eq, Clone)] +pub struct TrustedOperation { + /// Raw extrinsic representing that operation. + pub data: Extrinsic, + /// Number of bytes encoding of the operation requires. + pub bytes: usize, + /// TrustedOperation hash (unique) + pub hash: Hash, + /// TrustedOperation priority (higher = better) + pub priority: Priority, + /// At which block the operation becomes invalid? + pub valid_till: Longevity, + /// Tags required by the operation. + pub requires: Vec, + /// Tags that this operation provides. + pub provides: Vec, + /// Should that operation be propagated. + pub propagate: bool, + /// Source of that operation. + pub source: Source, +} + +impl AsRef for TrustedOperation { + fn as_ref(&self) -> &Extrinsic { + &self.data + } +} + +impl InPoolOperation for TrustedOperation { + type TrustedOperation = Extrinsic; + type Hash = Hash; + + fn data(&self) -> &Extrinsic { + &self.data + } + + fn hash(&self) -> &Hash { + &self.hash + } + + fn priority(&self) -> &Priority { + &self.priority + } + + fn longevity(&self) -> &Longevity { + &self.valid_till + } + + fn requires(&self) -> &[Tag] { + &self.requires + } + + fn provides(&self) -> &[Tag] { + &self.provides + } + + fn is_propagable(&self) -> bool { + self.propagate + } +} + +impl TrustedOperation { + /// Explicit operation clone. + /// + /// TrustedOperation should be cloned only if absolutely necessary && we want + /// every reason to be commented. That's why we `TrustedOperation` is not `Clone`, + /// but there's explicit `duplicate` method. + pub fn duplicate(&self) -> Self { + TrustedOperation { + data: self.data.clone(), + bytes: self.bytes, + hash: self.hash.clone(), + priority: self.priority, + source: self.source, + valid_till: self.valid_till, + requires: self.requires.clone(), + provides: self.provides.clone(), + propagate: self.propagate, + } + } +} + +impl fmt::Debug for TrustedOperation +where + Hash: fmt::Debug, + Extrinsic: fmt::Debug, +{ + fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { + fn print_tags(fmt: &mut fmt::Formatter, tags: &[Tag]) -> fmt::Result { + let mut it = tags.iter(); + if let Some(t) = it.next() { + write!(fmt, "{}", HexDisplay::from(t))?; + } + for t in it { + write!(fmt, ",{}", HexDisplay::from(t))?; + } + Ok(()) + } + + write!(fmt, "TrustedOperation {{ ")?; + write!(fmt, "hash: {:?}, ", &self.hash)?; + write!(fmt, "priority: {:?}, ", &self.priority)?; + write!(fmt, "valid_till: {:?}, ", &self.valid_till)?; + write!(fmt, "bytes: {:?}, ", &self.bytes)?; + write!(fmt, "propagate: {:?}, ", &self.propagate)?; + write!(fmt, "source: {:?}, ", &self.source)?; + write!(fmt, "requires: [")?; + print_tags(fmt, &self.requires)?; + write!(fmt, "], provides: [")?; + print_tags(fmt, &self.provides)?; + write!(fmt, "], ")?; + write!(fmt, "data: {:?}", &self.data)?; + write!(fmt, "}}")?; + Ok(()) + } +} + +/// Store last pruned tags for given number of invocations. +const RECENTLY_PRUNED_TAGS: usize = 2; + +/// TrustedOperation pool. +/// +/// Builds a dependency graph for all operations in the pool and returns +/// the ones that are currently ready to be executed. +/// +/// General note: +/// If function returns some operations it usually means that importing them +/// as-is for the second time will fail or produce unwanted results. +/// Most likely it is required to revalidate them and recompute set of +/// required tags. +#[derive(Debug)] +pub struct BasePool { + reject_future_operations: bool, + future: FutureTrustedOperations, + ready: ReadyOperations, + /// Store recently pruned tags (for last two invocations). + /// + /// This is used to make sure we don't accidentally put + /// operations to future in case they were just stuck in verification. + recently_pruned: [HashSet; RECENTLY_PRUNED_TAGS], + recently_pruned_index: usize, +} + +impl Default for BasePool { + fn default() -> Self { + Self::new(false) + } +} + +impl BasePool { + /// Create new pool given reject_future_operations flag. + pub fn new(reject_future_operations: bool) -> Self { + BasePool { + reject_future_operations, + future: Default::default(), + ready: Default::default(), + recently_pruned: Default::default(), + recently_pruned_index: 0, + } + } + + /// Temporary enables future operations, runs closure and then restores + /// `reject_future_operations` flag back to previous value. + /// + /// The closure accepts the mutable reference to the pool and original value + /// of the `reject_future_operations` flag. + pub(crate) fn with_futures_enabled( + &mut self, + closure: impl FnOnce(&mut Self, bool) -> T, + ) -> T { + let previous = self.reject_future_operations; + self.reject_future_operations = false; + let return_value = closure(self, previous); + self.reject_future_operations = previous; + return_value + } + + /// Returns if the operation for the given hash is already imported. + pub fn is_imported(&self, tx_hash: &Hash, shard: ShardIdentifier) -> bool { + self.future.contains(tx_hash, shard) || self.ready.contains(tx_hash, shard) + } + + /// Imports operations to the pool. + /// + /// The pool consists of two parts: Future and Ready. + /// The former contains operations that require some tags that are not yet provided by + /// other operations in the pool. + /// The latter contains operations that have all the requirements satisfied and are + /// ready to be included in the block. + pub fn import( + &mut self, + tx: TrustedOperation, + shard: ShardIdentifier, + ) -> error::Result> { + if self.is_imported(&tx.hash, shard) { + return Err(error::Error::AlreadyImported) + } + + let tx = WaitingTrustedOperations::new( + tx, + self.ready.provided_tags(shard), + &self.recently_pruned, + ); + trace!(target: "txpool", "[{:?}] {:?}", tx.operation.hash, tx); + debug!( + target: "txpool", + "[{:?}] Importing to {}", + tx.operation.hash, + if tx.is_ready() { "ready" } else { "future" } + ); + + // If all tags are not satisfied import to future. + if !tx.is_ready() { + if self.reject_future_operations { + return Err(error::Error::RejectedFutureTrustedOperation) + } + + let hash = tx.operation.hash.clone(); + self.future.import(tx, shard); + return Ok(Imported::Future { hash }) + } + + self.import_to_ready(tx, shard) + } + + /// Imports operations to ready queue. + /// + /// NOTE the operation has to have all requirements satisfied. + fn import_to_ready( + &mut self, + tx: WaitingTrustedOperations, + shard: ShardIdentifier, + ) -> error::Result> { + let hash = tx.operation.hash.clone(); + let mut promoted = vec![]; + let mut failed = vec![]; + let mut removed = vec![]; + + let mut first = true; + let mut to_import = vec![tx]; + + while let Some(tx) = to_import.pop() { + // find operation in Future that it unlocks + to_import.append(&mut self.future.satisfy_tags(&tx.operation.provides, shard)); + + // import this operation + let current_hash = tx.operation.hash.clone(); + match self.ready.import(tx, shard) { + Ok(mut replaced) => { + if !first { + promoted.push(current_hash); + } + // The operations were removed from the ready pool. We might attempt to re-import them. + removed.append(&mut replaced); + }, + // operation failed to be imported. + Err(e) => + if first { + debug!(target: "txpool", "[{:?}] Error importing", current_hash,); + return Err(e) + } else { + failed.push(current_hash); + }, + } + first = false; + } + + // An edge case when importing operation caused + // some future operations to be imported and that + // future operations pushed out current operation. + // This means that there is a cycle and the operations should + // be moved back to future, since we can't resolve it. + if removed.iter().any(|tx| tx.hash == hash) { + // We still need to remove all operations that we promoted + // since they depend on each other and will never get to the best iterator. + self.ready.remove_subtree(&promoted, shard); + + debug!(target: "txpool", "[{:?}] Cycle detected, bailing.", hash); + return Err(error::Error::CycleDetected) + } + + Ok(Imported::Ready { hash, promoted, failed, removed }) + } + + /// Returns an iterator over ready operations in the pool. + pub fn ready( + &self, + shard: ShardIdentifier, + ) -> impl Iterator>> { + self.ready.get(shard) + } + + /// Returns an iterator over all shards in the pool. + pub fn get_shards(&self) -> impl Iterator { + self.ready.get_shards() + } + + /// Returns an iterator over future operations in the pool. + pub fn futures( + &self, + shard: ShardIdentifier, + ) -> impl Iterator> { + self.future.all(shard) + } + + /// Returns pool operations given list of hashes. + /// + /// Includes both ready and future pool. For every hash in the `hashes` + /// iterator an `Option` is produced (so the resulting `Vec` always have the same length). + pub fn by_hashes( + &self, + hashes: &[Hash], + shard: ShardIdentifier, + ) -> Vec>>> { + let ready = self.ready.by_hashes(hashes, shard); + let future = self.future.by_hashes(hashes, shard); + + ready.into_iter().zip(future).map(|(a, b)| a.or(b)).collect() + } + + /// Returns pool operation by hash. + pub fn ready_by_hash( + &self, + hash: &Hash, + shard: ShardIdentifier, + ) -> Option>> { + self.ready.by_hash(hash, shard) + } + + /// Makes sure that the operations in the queues stay within provided limits. + /// + /// Removes and returns worst operations from the queues and all operations that depend on them. + /// Technically the worst operation should be evaluated by computing the entire pending set. + /// We use a simplified approach to remove the operation that occupies the pool for the longest time. + pub fn enforce_limits( + &mut self, + ready: &Limit, + future: &Limit, + shard: ShardIdentifier, + ) -> Vec>> { + let mut removed = vec![]; + + while ready.is_exceeded(self.ready.len(shard), self.ready.bytes(shard)) { + // find the worst operation + let minimal = self.ready.fold( + |minimal, current| { + let operation = ¤t.operation; + match minimal { + None => Some(operation.clone()), + Some(ref tx) if tx.insertion_id > operation.insertion_id => + Some(operation.clone()), + other => other, + } + }, + shard, + ); + + if let Some(minimal) = minimal { + removed.append(&mut self.remove_subtree(&[minimal.operation.hash.clone()], shard)) + } else { + break + } + } + + while future.is_exceeded(self.future.len(shard), self.future.bytes(shard)) { + // find the worst operation + let minimal = self.future.fold( + |minimal, current| { + match minimal { + None => Some(current.clone()), + /*Some(ref tx) if tx.imported_at > current.imported_at => { + Some(current.clone()) + },*/ + other => other, + } + }, + shard, + ); + + if let Some(minimal) = minimal { + removed.append(&mut self.remove_subtree(&[minimal.operation.hash.clone()], shard)) + } else { + break + } + } + + removed + } + + /// Removes all operations represented by the hashes and all other operations + /// that depend on them. + /// + /// Returns a list of actually removed operations. + /// NOTE some operations might still be valid, but were just removed because + /// they were part of a chain, you may attempt to re-import them later. + /// NOTE If you want to remove ready operations that were already used + /// and you don't want them to be stored in the pool use `prune_tags` method. + pub fn remove_subtree( + &mut self, + hashes: &[Hash], + shard: ShardIdentifier, + ) -> Vec>> { + let mut removed = self.ready.remove_subtree(hashes, shard); + removed.extend(self.future.remove(hashes, shard)); + removed + } + + /// Removes and returns all operations from the future queue. + pub fn clear_future(&mut self, shard: ShardIdentifier) -> Vec>> { + self.future.clear(shard) + } + + /// Prunes operations that provide given list of tags. + /// + /// This will cause all operations that provide these tags to be removed from the pool, + /// but unlike `remove_subtree`, dependent operations are not touched. + /// Additional operations from future queue might be promoted to ready if you satisfy tags + /// that the pool didn't previously know about. + pub fn prune_tags( + &mut self, + tags: impl IntoIterator, + shard: ShardIdentifier, + ) -> PruneStatus { + let mut to_import = vec![]; + let mut pruned = vec![]; + let recently_pruned = &mut self.recently_pruned[self.recently_pruned_index]; + self.recently_pruned_index = (self.recently_pruned_index + 1) % RECENTLY_PRUNED_TAGS; + recently_pruned.clear(); + + for tag in tags { + // make sure to promote any future operations that could be unlocked + to_import.append(&mut self.future.satisfy_tags(iter::once(&tag), shard)); + // and actually prune operations in ready queue + pruned.append(&mut self.ready.prune_tags(tag.clone(), shard)); + // store the tags for next submission + recently_pruned.insert(tag); + } + + let mut promoted = vec![]; + let mut failed = vec![]; + for tx in to_import { + let hash = tx.operation.hash.clone(); + match self.import_to_ready(tx, shard) { + Ok(res) => promoted.push(res), + Err(_e) => { + warn!(target: "txpool", "[{:?}] Failed to promote during pruning", hash); + failed.push(hash) + }, + } + } + + PruneStatus { promoted, failed, pruned } + } + + /// Get pool status. + pub fn status(&self, shard: ShardIdentifier) -> PoolStatus { + PoolStatus { + ready: self.ready.len(shard), + ready_bytes: self.ready.bytes(shard), + future: self.future.len(shard), + future_bytes: self.future.bytes(shard), + } + } +} + +/// Queue limits +#[derive(Debug, Clone)] +pub struct Limit { + /// Maximal number of operations in the queue. + pub count: usize, + /// Maximal size of encodings of all operations in the queue. + pub total_bytes: usize, +} + +impl Limit { + /// Returns true if any of the provided values exceeds the limit. + pub fn is_exceeded(&self, count: usize, bytes: usize) -> bool { + self.count < count || self.total_bytes < bytes + } +} + +#[cfg(test)] +pub mod tests { + + use super::*; + use alloc::borrow::ToOwned; + + type Hash = u64; + + fn test_pool() -> BasePool> { + BasePool::default() + } + + #[test] + pub fn test_should_import_transaction_to_ready() { + // given + let mut pool = test_pool(); + let shard = ShardIdentifier::default(); + + // when + pool.import( + TrustedOperation { + data: vec![1u8], + bytes: 1, + hash: 1u64, + priority: 5u64, + valid_till: 64u64, + requires: vec![], + provides: vec![vec![1]], + propagate: true, + source: Source::External, + }, + shard, + ) + .unwrap(); + + // then + assert_eq!(pool.ready(shard).count(), 1); + assert_eq!(pool.ready.len(shard), 1); + } + + #[test] + pub fn test_should_not_import_same_transaction_twice() { + // given + let mut pool = test_pool(); + let shard = ShardIdentifier::default(); + + // when + pool.import( + TrustedOperation { + data: vec![1u8], + bytes: 1, + hash: 1, + priority: 5u64, + valid_till: 64u64, + requires: vec![], + provides: vec![vec![1]], + propagate: true, + source: Source::External, + }, + shard, + ) + .unwrap(); + pool.import( + TrustedOperation { + data: vec![1u8], + bytes: 1, + hash: 1, + priority: 5u64, + valid_till: 64u64, + requires: vec![], + provides: vec![vec![1]], + propagate: true, + source: Source::External, + }, + shard, + ) + .unwrap_err(); + + // then + assert_eq!(pool.ready(shard).count(), 1); + assert_eq!(pool.ready.len(shard), 1); + } + + #[test] + pub fn test_should_import_transaction_to_future_and_promote_it_later() { + // given + let mut pool = test_pool(); + let shard = ShardIdentifier::default(); + + // when + pool.import( + TrustedOperation { + data: vec![1u8], + bytes: 1, + hash: 1, + priority: 5u64, + valid_till: 64u64, + requires: vec![vec![0]], + provides: vec![vec![1]], + propagate: true, + source: Source::External, + }, + shard, + ) + .unwrap(); + assert_eq!(pool.ready(shard).count(), 0); + assert_eq!(pool.ready.len(shard), 0); + pool.import( + TrustedOperation { + data: vec![2u8], + bytes: 1, + hash: 2, + priority: 5u64, + valid_till: 64u64, + requires: vec![], + provides: vec![vec![0]], + propagate: true, + source: Source::External, + }, + shard, + ) + .unwrap(); + + // then + assert_eq!(pool.ready(shard).count(), 2); + assert_eq!(pool.ready.len(shard), 2); + } + + #[test] + pub fn test_should_promote_a_subgraph() { + // given + let mut pool = test_pool(); + let shard = ShardIdentifier::default(); + + // when + pool.import( + TrustedOperation { + data: vec![1u8], + bytes: 1, + hash: 1, + priority: 5u64, + valid_till: 64u64, + requires: vec![vec![0]], + provides: vec![vec![1]], + propagate: true, + source: Source::External, + }, + shard, + ) + .unwrap(); + pool.import( + TrustedOperation { + data: vec![3u8], + bytes: 1, + hash: 3, + priority: 5u64, + valid_till: 64u64, + requires: vec![vec![2]], + provides: vec![], + propagate: true, + source: Source::External, + }, + shard, + ) + .unwrap(); + pool.import( + TrustedOperation { + data: vec![2u8], + bytes: 1, + hash: 2, + priority: 5u64, + valid_till: 64u64, + requires: vec![vec![1]], + provides: vec![vec![3], vec![2]], + propagate: true, + source: Source::External, + }, + shard, + ) + .unwrap(); + pool.import( + TrustedOperation { + data: vec![4u8], + bytes: 1, + hash: 4, + priority: 1_000u64, + valid_till: 64u64, + requires: vec![vec![3], vec![4]], + provides: vec![], + propagate: true, + source: Source::External, + }, + shard, + ) + .unwrap(); + assert_eq!(pool.ready(shard).count(), 0); + assert_eq!(pool.ready.len(shard), 0); + + let res = pool + .import( + TrustedOperation { + data: vec![5u8], + bytes: 1, + hash: 5, + priority: 5u64, + valid_till: 64u64, + requires: vec![], + provides: vec![vec![0], vec![4]], + propagate: true, + source: Source::External, + }, + shard, + ) + .unwrap(); + + // then + let mut it = pool.ready(shard).into_iter().map(|tx| tx.data[0]); + + assert_eq!(it.next(), Some(5)); + assert_eq!(it.next(), Some(1)); + assert_eq!(it.next(), Some(2)); + assert_eq!(it.next(), Some(4)); + assert_eq!(it.next(), Some(3)); + assert_eq!(it.next(), None); + assert_eq!( + res, + Imported::Ready { + hash: 5, + promoted: vec![1, 2, 3, 4], + failed: vec![], + removed: vec![] + } + ); + } + + #[test] + pub fn test_should_handle_a_cycle() { + // given + let shard = ShardIdentifier::default(); + let mut pool = test_pool(); + pool.import( + TrustedOperation { + data: vec![1u8], + bytes: 1, + hash: 1, + priority: 5u64, + valid_till: 64u64, + requires: vec![vec![0]], + provides: vec![vec![1]], + propagate: true, + source: Source::External, + }, + shard, + ) + .unwrap(); + pool.import( + TrustedOperation { + data: vec![3u8], + bytes: 1, + hash: 3, + priority: 5u64, + valid_till: 64u64, + requires: vec![vec![1]], + provides: vec![vec![2]], + propagate: true, + source: Source::External, + }, + shard, + ) + .unwrap(); + assert_eq!(pool.ready(shard).count(), 0); + assert_eq!(pool.ready.len(shard), 0); + + // when + pool.import( + TrustedOperation { + data: vec![2u8], + bytes: 1, + hash: 2, + priority: 5u64, + valid_till: 64u64, + requires: vec![vec![2]], + provides: vec![vec![0]], + propagate: true, + source: Source::External, + }, + shard, + ) + .unwrap(); + + // then + { + let mut it = pool.ready(shard).into_iter().map(|tx| tx.data[0]); + assert_eq!(it.next(), None); + } + // all operations occupy the Future queue - it's fine + assert_eq!(pool.future.len(shard), 3); + + // let's close the cycle with one additional operation + let res = pool + .import( + TrustedOperation { + data: vec![4u8], + bytes: 1, + hash: 4, + priority: 50u64, + valid_till: 64u64, + requires: vec![], + provides: vec![vec![0]], + propagate: true, + source: Source::External, + }, + shard, + ) + .unwrap(); + let mut it = pool.ready(shard).into_iter().map(|tx| tx.data[0]); + assert_eq!(it.next(), Some(4)); + assert_eq!(it.next(), Some(1)); + assert_eq!(it.next(), Some(3)); + assert_eq!(it.next(), None); + assert_eq!( + res, + Imported::Ready { hash: 4, promoted: vec![1, 3], failed: vec![2], removed: vec![] } + ); + assert_eq!(pool.future.len(shard), 0); + } + + #[test] + pub fn test_should_handle_a_cycle_with_low_priority() { + // given + let mut pool = test_pool(); + let shard = ShardIdentifier::default(); + pool.import( + TrustedOperation { + data: vec![1u8], + bytes: 1, + hash: 1, + priority: 5u64, + valid_till: 64u64, + requires: vec![vec![0]], + provides: vec![vec![1]], + propagate: true, + source: Source::External, + }, + shard, + ) + .unwrap(); + pool.import( + TrustedOperation { + data: vec![3u8], + bytes: 1, + hash: 3, + priority: 5u64, + valid_till: 64u64, + requires: vec![vec![1]], + provides: vec![vec![2]], + propagate: true, + source: Source::External, + }, + shard, + ) + .unwrap(); + assert_eq!(pool.ready(shard).count(), 0); + assert_eq!(pool.ready.len(shard), 0); + + // when + pool.import( + TrustedOperation { + data: vec![2u8], + bytes: 1, + hash: 2, + priority: 5u64, + valid_till: 64u64, + requires: vec![vec![2]], + provides: vec![vec![0]], + propagate: true, + source: Source::External, + }, + shard, + ) + .unwrap(); + + // then + { + let mut it = pool.ready(shard).into_iter().map(|tx| tx.data[0]); + assert_eq!(it.next(), None); + } + // all operations occupy the Future queue - it's fine + assert_eq!(pool.future.len(shard), 3); + + // let's close the cycle with one additional operation + let err = pool + .import( + TrustedOperation { + data: vec![4u8], + bytes: 1, + hash: 4, + priority: 1u64, // lower priority than Tx(2) + valid_till: 64u64, + requires: vec![], + provides: vec![vec![0]], + propagate: true, + source: Source::External, + }, + shard, + ) + .unwrap_err(); + let mut it = pool.ready(shard).into_iter().map(|tx| tx.data[0]); + assert_eq!(it.next(), None); + assert_eq!(pool.ready.len(shard), 0); + assert_eq!(pool.future.len(shard), 0); + if let error::Error::CycleDetected = err { + } else { + assert!(false, "Invalid error kind: {:?}", err); + } + } + + #[test] + pub fn test_can_track_heap_size() { + let mut pool = test_pool(); + let shard = ShardIdentifier::default(); + pool.import( + TrustedOperation { + data: vec![5u8; 1024], + bytes: 1, + hash: 5, + priority: 5u64, + valid_till: 64u64, + requires: vec![], + provides: vec![vec![0], vec![4]], + propagate: true, + source: Source::External, + }, + shard, + ) + .expect("import 1 should be ok"); + pool.import( + TrustedOperation { + data: vec![3u8; 1024], + bytes: 1, + hash: 7, + priority: 5u64, + valid_till: 64u64, + requires: vec![], + provides: vec![vec![2], vec![7]], + propagate: true, + source: Source::External, + }, + shard, + ) + .expect("import 2 should be ok"); + + //assert!(parity_util_mem::malloc_size(&pool) > 5000); + } + + #[test] + pub fn test_should_remove_invalid_transactions() { + // given + let shard = ShardIdentifier::default(); + let mut pool = test_pool(); + pool.import( + TrustedOperation { + data: vec![5u8], + bytes: 1, + hash: 5, + priority: 5u64, + valid_till: 64u64, + requires: vec![], + provides: vec![vec![0], vec![4]], + propagate: true, + source: Source::External, + }, + shard, + ) + .unwrap(); + pool.import( + TrustedOperation { + data: vec![1u8], + bytes: 1, + hash: 1, + priority: 5u64, + valid_till: 64u64, + requires: vec![vec![0]], + provides: vec![vec![1]], + propagate: true, + source: Source::External, + }, + shard, + ) + .unwrap(); + pool.import( + TrustedOperation { + data: vec![3u8], + bytes: 1, + hash: 3, + priority: 5u64, + valid_till: 64u64, + requires: vec![vec![2]], + provides: vec![], + propagate: true, + source: Source::External, + }, + shard, + ) + .unwrap(); + pool.import( + TrustedOperation { + data: vec![2u8], + bytes: 1, + hash: 2, + priority: 5u64, + valid_till: 64u64, + requires: vec![vec![1]], + provides: vec![vec![3], vec![2]], + propagate: true, + source: Source::External, + }, + shard, + ) + .unwrap(); + pool.import( + TrustedOperation { + data: vec![4u8], + bytes: 1, + hash: 4, + priority: 1_000u64, + valid_till: 64u64, + requires: vec![vec![3], vec![4]], + provides: vec![], + propagate: true, + source: Source::External, + }, + shard, + ) + .unwrap(); + // future + pool.import( + TrustedOperation { + data: vec![6u8], + bytes: 1, + hash: 6, + priority: 1_000u64, + valid_till: 64u64, + requires: vec![vec![11]], + provides: vec![], + propagate: true, + source: Source::External, + }, + shard, + ) + .unwrap(); + assert_eq!(pool.ready(shard).count(), 5); + assert_eq!(pool.future.len(shard), 1); + + // when + pool.remove_subtree(&[6, 1], shard); + + // then + assert_eq!(pool.ready(shard).count(), 1); + assert_eq!(pool.future.len(shard), 0); + } + + #[test] + pub fn test_should_prune_ready_transactions() { + // given + let mut pool = test_pool(); + let shard = ShardIdentifier::default(); + // future (waiting for 0) + pool.import( + TrustedOperation { + data: vec![5u8], + bytes: 1, + hash: 5, + priority: 5u64, + valid_till: 64u64, + requires: vec![vec![0]], + provides: vec![vec![100]], + propagate: true, + source: Source::External, + }, + shard, + ) + .unwrap(); + // ready + pool.import( + TrustedOperation { + data: vec![1u8], + bytes: 1, + hash: 1, + priority: 5u64, + valid_till: 64u64, + requires: vec![], + provides: vec![vec![1]], + propagate: true, + source: Source::External, + }, + shard, + ) + .unwrap(); + pool.import( + TrustedOperation { + data: vec![2u8], + bytes: 1, + hash: 2, + priority: 5u64, + valid_till: 64u64, + requires: vec![vec![2]], + provides: vec![vec![3]], + propagate: true, + source: Source::External, + }, + shard, + ) + .unwrap(); + pool.import( + TrustedOperation { + data: vec![3u8], + bytes: 1, + hash: 3, + priority: 5u64, + valid_till: 64u64, + requires: vec![vec![1]], + provides: vec![vec![2]], + propagate: true, + source: Source::External, + }, + shard, + ) + .unwrap(); + pool.import( + TrustedOperation { + data: vec![4u8], + bytes: 1, + hash: 4, + priority: 1_000u64, + valid_till: 64u64, + requires: vec![vec![3], vec![2]], + provides: vec![vec![4]], + propagate: true, + source: Source::External, + }, + shard, + ) + .unwrap(); + + assert_eq!(pool.ready(shard).count(), 4); + assert_eq!(pool.future.len(shard), 1); + + // when + let result = pool.prune_tags(vec![vec![0], vec![2]], shard); + + // then + assert_eq!(result.pruned.len(), 2); + assert_eq!(result.failed.len(), 0); + assert_eq!( + result.promoted[0], + Imported::Ready { hash: 5, promoted: vec![], failed: vec![], removed: vec![] } + ); + assert_eq!(result.promoted.len(), 1); + assert_eq!(pool.future.len(shard), 0); + assert_eq!(pool.ready.len(shard), 3); + assert_eq!(pool.ready(shard).count(), 3); + } + + #[test] + pub fn test_transaction_debug() { + assert_eq!( + format!( + "{:?}", + TrustedOperation { + data: vec![4u8], + bytes: 1, + hash: 4, + priority: 1_000u64, + valid_till: 64u64, + requires: vec![vec![3], vec![2]], + provides: vec![vec![4]], + propagate: true, + source: Source::External, + } + ), + "TrustedOperation { \ +hash: 4, priority: 1000, valid_till: 64, bytes: 1, propagate: true, \ +source: External, requires: [03,02], provides: [04], data: [4]}" + .to_owned() + ); + } + + #[test] + pub fn test_transaction_propagation() { + assert!(TrustedOperation { + data: vec![4u8], + bytes: 1, + hash: 4, + priority: 1_000u64, + valid_till: 64u64, + requires: vec![vec![3], vec![2]], + provides: vec![vec![4]], + propagate: true, + source: Source::External, + } + .is_propagable()); + + assert!(!TrustedOperation { + data: vec![4u8], + bytes: 1, + hash: 4, + priority: 1_000u64, + valid_till: 64u64, + requires: vec![vec![3], vec![2]], + provides: vec![vec![4]], + propagate: false, + source: Source::External, + } + .is_propagable()); + } + + #[test] + pub fn test_should_reject_future_transactions() { + // given + let mut pool = test_pool(); + let shard = ShardIdentifier::default(); + + // when + pool.reject_future_operations = true; + + // then + let err = pool.import( + TrustedOperation { + data: vec![5u8], + bytes: 1, + hash: 5, + priority: 5u64, + valid_till: 64u64, + requires: vec![vec![0]], + provides: vec![], + propagate: true, + source: Source::External, + }, + shard, + ); + + if let Err(error::Error::RejectedFutureTrustedOperation) = err { + } else { + assert!(false, "Invalid error kind: {:?}", err); + } + } + + #[test] + pub fn test_should_clear_future_queue() { + // given + let mut pool = test_pool(); + let shard = ShardIdentifier::default(); + + // when + pool.import( + TrustedOperation { + data: vec![5u8], + bytes: 1, + hash: 5, + priority: 5u64, + valid_till: 64u64, + requires: vec![vec![0]], + provides: vec![], + propagate: true, + source: Source::External, + }, + shard, + ) + .unwrap(); + + // then + assert_eq!(pool.future.len(shard), 1); + + // and then when + assert_eq!(pool.clear_future(shard).len(), 1); + + // then + assert_eq!(pool.future.len(shard), 0); + } + + #[test] + pub fn test_should_accept_future_transactions_when_explicitly_asked_to() { + // given + let mut pool = test_pool(); + pool.reject_future_operations = true; + let shard = ShardIdentifier::default(); + + // when + let flag_value = pool.with_futures_enabled(|pool, flag| { + pool.import( + TrustedOperation { + data: vec![5u8], + bytes: 1, + hash: 5, + priority: 5u64, + valid_till: 64u64, + requires: vec![vec![0]], + provides: vec![], + propagate: true, + source: Source::External, + }, + shard, + ) + .unwrap(); + + flag + }); + + // then + assert!(flag_value); + assert!(pool.reject_future_operations); + assert_eq!(pool.future.len(shard), 1); + } +} diff --git a/tee-worker/core-primitives/top-pool/src/basic_pool.rs b/tee-worker/core-primitives/top-pool/src/basic_pool.rs new file mode 100644 index 0000000000..e263aa3fcc --- /dev/null +++ b/tee-worker/core-primitives/top-pool/src/basic_pool.rs @@ -0,0 +1,247 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +pub extern crate alloc; + +#[cfg(all(not(feature = "std"), feature = "sgx"))] +use crate::sgx_reexport_prelude::*; + +#[cfg(all(not(feature = "std"), feature = "sgx"))] +use std::sync::SgxMutex as Mutex; + +#[cfg(feature = "std")] +use std::sync::Mutex; + +use crate::{ + base_pool::TrustedOperation, + error::IntoPoolError, + pool::{ChainApi, ExtrinsicHash, Options as PoolOptions, Pool}, + primitives::{ + ImportNotificationStream, PoolFuture, PoolStatus, TrustedOperationPool, + TrustedOperationSource, TxHash, + }, +}; +use alloc::{boxed::Box, string::String, sync::Arc}; +use core::pin::Pin; +use ita_stf::{ShardIdentifier, TrustedOperation as StfTrustedOperation}; +use itc_direct_rpc_server::SendRpcResponse; +use its_primitives::types::BlockHash as SidechainBlockHash; +use jsonrpc_core::futures::{ + channel::oneshot, + future::{ready, Future, FutureExt}, +}; +use sp_runtime::{ + generic::BlockId, + traits::{Block as BlockT, NumberFor, Zero}, +}; +use std::{collections::HashMap, vec, vec::Vec}; + +type BoxedReadyIterator = + Box>> + Send>; + +type ReadyIteratorFor = BoxedReadyIterator, StfTrustedOperation>; + +type PolledIterator = Pin> + Send>>; + +struct ReadyPoll { + updated_at: NumberFor, + pollers: Vec<(NumberFor, oneshot::Sender)>, +} + +impl Default for ReadyPoll { + fn default() -> Self { + Self { updated_at: NumberFor::::zero(), pollers: Default::default() } + } +} + +impl ReadyPoll { + #[allow(unused)] + fn trigger(&mut self, number: NumberFor, iterator_factory: impl Fn() -> T) { + self.updated_at = number; + + let mut idx = 0; + while idx < self.pollers.len() { + if self.pollers[idx].0 <= number { + let poller_sender = self.pollers.swap_remove(idx); + let _ = poller_sender.1.send(iterator_factory()); + } else { + idx += 1; + } + } + } + + fn add(&mut self, number: NumberFor) -> oneshot::Receiver { + let (sender, receiver) = oneshot::channel(); + self.pollers.push((number, sender)); + receiver + } + + fn updated_at(&self) -> NumberFor { + self.updated_at + } +} + +/// Basic implementation of operation pool that can be customized by providing PoolApi. +pub struct BasicPool +where + Block: BlockT, + PoolApi: ChainApi + 'static, + RpcResponse: SendRpcResponse>, +{ + pool: Arc>, + _api: Arc, + ready_poll: Arc, Block>>>, +} + +impl BasicPool +where + Block: BlockT, + PoolApi: ChainApi + 'static, + RpcResponse: SendRpcResponse>, +{ + /// Create new basic operation pool with provided api and custom + /// revalidation type. + pub fn create( + options: PoolOptions, + pool_api: Arc, + rpc_response_sender: Arc, + //prometheus: Option<&PrometheusRegistry>, + //revalidation_type: RevalidationType, + //spawner: impl SpawnNamed, + ) -> Self + where + ::Error: IntoPoolError, + { + let pool = Arc::new(Pool::new(options, pool_api.clone(), rpc_response_sender)); + BasicPool { _api: pool_api, pool, ready_poll: Default::default() } + } +} + +// FIXME: obey clippy +#[allow(clippy::type_complexity)] +impl TrustedOperationPool for BasicPool +where + Block: BlockT, + PoolApi: ChainApi + 'static, + ::Error: IntoPoolError, + RpcResponse: SendRpcResponse> + 'static, +{ + type Block = PoolApi::Block; + type Hash = ExtrinsicHash; + type InPoolOperation = TrustedOperation, StfTrustedOperation>; + type Error = PoolApi::Error; + + fn submit_at( + &self, + at: &BlockId, + source: TrustedOperationSource, + ops: Vec, + shard: ShardIdentifier, + ) -> PoolFuture, Self::Error>>, Self::Error> { + let pool = self.pool.clone(); + let at = *at; + async move { pool.submit_at(&at, source, ops, shard).await }.boxed() + } + + fn submit_one( + &self, + at: &BlockId, + source: TrustedOperationSource, + op: StfTrustedOperation, + shard: ShardIdentifier, + ) -> PoolFuture, Self::Error> { + let pool = self.pool.clone(); + let at = *at; + async move { pool.submit_one(&at, source, op, shard).await }.boxed() + } + + fn submit_and_watch( + &self, + at: &BlockId, + source: TrustedOperationSource, + xt: StfTrustedOperation, + shard: ShardIdentifier, + ) -> PoolFuture, Self::Error> { + let at = *at; + let pool = self.pool.clone(); + async move { pool.submit_and_watch(&at, source, xt, shard).await }.boxed() + } + + fn ready_at( + &self, + at: NumberFor, + shard: ShardIdentifier, + ) -> PolledIterator { + if self.ready_poll.lock().unwrap().updated_at() >= at { + let iterator: ReadyIteratorFor = + Box::new(self.pool.validated_pool().ready(shard)); + return Box::pin(ready(iterator)) + } + + Box::pin(self.ready_poll.lock().unwrap().add(at).map(|received| { + received.unwrap_or_else(|e| { + log::warn!("Error receiving pending set: {:?}", e); + Box::new(vec![].into_iter()) + }) + })) + } + + fn ready(&self, shard: ShardIdentifier) -> ReadyIteratorFor { + Box::new(self.pool.validated_pool().ready(shard)) + } + + fn shards(&self) -> Vec { + self.pool.validated_pool().shards() + } + + fn remove_invalid( + &self, + hashes: &[TxHash], + shard: ShardIdentifier, + inblock: bool, + ) -> Vec> { + self.pool.validated_pool().remove_invalid(hashes, shard, inblock) + } + + fn status(&self, shard: ShardIdentifier) -> PoolStatus { + self.pool.validated_pool().status(shard) + } + + fn import_notification_stream(&self) -> ImportNotificationStream> { + self.pool.validated_pool().import_notification_stream() + } + + fn on_broadcasted(&self, propagations: HashMap, Vec>) { + self.pool.validated_pool().on_broadcasted(propagations) + } + + fn hash_of(&self, xt: &StfTrustedOperation) -> TxHash { + self.pool.hash_of(xt) + } + + fn ready_transaction( + &self, + hash: &TxHash, + shard: ShardIdentifier, + ) -> Option> { + self.pool.validated_pool().ready_by_hash(hash, shard) + } + + fn on_block_imported(&self, hashes: &[Self::Hash], block_hash: SidechainBlockHash) { + self.pool.validated_pool().on_block_imported(hashes, block_hash); + } +} diff --git a/tee-worker/core-primitives/top-pool/src/error.rs b/tee-worker/core-primitives/top-pool/src/error.rs new file mode 100644 index 0000000000..47029b30e1 --- /dev/null +++ b/tee-worker/core-primitives/top-pool/src/error.rs @@ -0,0 +1,95 @@ +// This file is part of Substrate. + +// Copyright (C) 2018-2020 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! TrustedOperation pool errors. + +use derive_more::{Display, From}; +use sp_runtime::transaction_validity::TransactionPriority as Priority; +use std::string::String; + +/// TrustedOperation pool result. +pub type Result = std::result::Result; + +/// TrustedOperation pool error type. +#[derive(Debug, From, Display)] +#[allow(missing_docs)] +pub enum Error { + #[display(fmt = "Unknown trusted operation")] + UnknownTrustedOperation, + + #[display(fmt = "Invalid trusted operation")] + InvalidTrustedOperation, + + /// Incorrect extrinsic format. + + /// The operation validity returned no "provides" tag. + /// + /// Such operations are not accepted to the pool, since we use those tags + /// to define identity of operations (occupance of the same "slot"). + #[display(fmt = "Trusted Operation does not provide any tags, so the pool can't identify it")] + NoTagsProvided, + + #[display(fmt = "Trusted Operation temporarily Banned")] + TemporarilyBanned, + + #[display(fmt = "Already imported")] + AlreadyImported, + + #[display(fmt = "Too low priority")] + TooLowPriority(Priority), + + #[display(fmt = "TrustedOperation with cyclic dependency")] + CycleDetected, + + #[display(fmt = "TrustedOperation couldn't enter the pool because of the limit")] + ImmediatelyDropped, + + #[from(ignore)] + #[display(fmt = "Invalid Block")] + InvalidBlockId(String), + + #[display(fmt = "The pool is not accepting future trusted operations")] + RejectedFutureTrustedOperation, + + #[display(fmt = "Extrinsic verification error")] + #[from(ignore)] + Verification, + + #[display(fmt = "Failed to send result of trusted operation to RPC client")] + FailedToSendUpdateToRpcClient(String), + + #[display(fmt = "Failed to unlock pool (mutex)")] + UnlockError, +} + +/// TrustedOperation pool error conversion. +pub trait IntoPoolError: Send + Sized { + /// Try to extract original `Error` + /// + /// This implementation is optional and used only to + /// provide more descriptive error messages for end users + /// of RPC API. + fn into_pool_error(self) -> std::result::Result { + Err(self) + } +} + +impl IntoPoolError for Error { + fn into_pool_error(self) -> std::result::Result { + Ok(self) + } +} diff --git a/tee-worker/core-primitives/top-pool/src/future.rs b/tee-worker/core-primitives/top-pool/src/future.rs new file mode 100644 index 0000000000..e19da72535 --- /dev/null +++ b/tee-worker/core-primitives/top-pool/src/future.rs @@ -0,0 +1,316 @@ +// This file is part of Substrate. + +// Copyright (C) 2018-2021 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +pub extern crate alloc; + +use crate::base_pool::TrustedOperation; +use alloc::{boxed::Box, fmt, sync::Arc, vec, vec::Vec}; +use core::hash; +use ita_stf::ShardIdentifier; +use sp_core::hexdisplay::HexDisplay; +use sp_runtime::transaction_validity::TransactionTag as Tag; +use std::{ + collections::{HashMap, HashSet}, + time::Instant, +}; + +/// TrustedOperation with partially satisfied dependencies. +pub struct WaitingTrustedOperations { + /// TrustedOperation details. + pub operation: Arc>, + /// Tags that are required and have not been satisfied yet by other operations in the pool. + pub missing_tags: HashSet, + /// Time of import to the Future Queue. + pub imported_at: Instant, +} + +impl fmt::Debug for WaitingTrustedOperations { + fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { + write!(fmt, "WaitingTrustedOperations {{ ")?; + //write!(fmt, "imported_at: {:?}, ", self.imported_at)?; + write!(fmt, "operation: {:?}, ", self.operation)?; + write!(fmt, "missing_tags: {{")?; + let mut it = self.missing_tags.iter().map(HexDisplay::from); + if let Some(tag) = it.next() { + write!(fmt, "{}", tag)?; + } + for tag in it { + write!(fmt, ", {}", tag)?; + } + write!(fmt, " }}}}") + } +} + +impl Clone for WaitingTrustedOperations { + fn clone(&self) -> Self { + WaitingTrustedOperations { + operation: self.operation.clone(), + missing_tags: self.missing_tags.clone(), + imported_at: self.imported_at, + } + } +} + +impl WaitingTrustedOperations { + /// Creates a new `WaitingTrustedOperations`. + /// + /// Computes the set of missing tags based on the requirements and tags that + /// are provided by all operations in the ready queue. + pub fn new( + operation: TrustedOperation, + provided: Option<&HashMap>, + recently_pruned: &[HashSet], + ) -> Self { + let missing_tags = operation + .requires + .iter() + .filter(|tag| { + // is true if the tag is already satisfied either via operation in the pool + // or one that was recently included. + + let is_provided = recently_pruned.iter().any(|x| x.contains(&**tag)) + || match provided { + Some(tags) => tags.contains_key(&**tag), + None => false, + }; + + !is_provided + }) + .cloned() + .collect(); + + WaitingTrustedOperations { + operation: Arc::new(operation), + missing_tags, + imported_at: Instant::now(), + } + } + + /// Marks the tag as satisfied. + // FIXME: obey clippy + #[allow(clippy::ptr_arg)] + pub fn satisfy_tag(&mut self, tag: &Tag) { + self.missing_tags.remove(tag); + } + + /// Returns true if operation has all requirements satisfied. + pub fn is_ready(&self) -> bool { + self.missing_tags.is_empty() + } +} + +/// A pool of operations that are not yet ready to be included in the block. +/// +/// Contains operations that are still awaiting for some other operations that +/// could provide a tag that they require. +#[derive(Debug)] +pub struct FutureTrustedOperations { + /// tags that are not yet provided by any operation and we await for them + wanted_tags: HashMap>>, + /// Transactions waiting for a particular other operation + waiting: HashMap>>, +} + +impl Default for FutureTrustedOperations { + fn default() -> Self { + FutureTrustedOperations { wanted_tags: Default::default(), waiting: Default::default() } + } +} + +const WAITING_PROOF: &str = r"# +In import we always insert to `waiting` if we push to `wanted_tags`; +when removing from `waiting` we always clear `wanted_tags`; +every hash from `wanted_tags` is always present in `waiting`; +qed +#"; + +#[allow(clippy::len_without_is_empty)] +impl FutureTrustedOperations { + /// Import operation to Future queue. + /// + /// Only operations that don't have all their tags satisfied should occupy + /// the Future queue. + /// As soon as required tags are provided by some other operations that are ready + /// we should remove the operations from here and move them to the Ready queue. + pub fn import(&mut self, tx: WaitingTrustedOperations, shard: ShardIdentifier) { + assert!(!tx.is_ready(), "TrustedOperation is ready."); + if let Some(tx_pool_waiting) = self.waiting.get(&shard) { + assert!( + !tx_pool_waiting.contains_key(&tx.operation.hash), + "TrustedOperation is already imported." + ); + } + + let tx_pool_waiting_map = self.waiting.entry(shard).or_insert_with(HashMap::new); + let tx_pool_wanted_map = self.wanted_tags.entry(shard).or_insert_with(HashMap::new); + // Add all tags that are missing + for tag in &tx.missing_tags { + let entry = tx_pool_wanted_map.entry(tag.clone()).or_insert_with(HashSet::new); + entry.insert(tx.operation.hash.clone()); + } + + // Add the operation to a by-hash waiting map + tx_pool_waiting_map.insert(tx.operation.hash.clone(), tx); + } + + /// Returns true if given hash is part of the queue. + pub fn contains(&self, hash: &Hash, shard: ShardIdentifier) -> bool { + if let Some(tx_pool_waiting) = self.waiting.get(&shard) { + return tx_pool_waiting.contains_key(hash) + } + false + } + + /// Returns a list of known operations + pub fn by_hashes( + &self, + hashes: &[Hash], + shard: ShardIdentifier, + ) -> Vec>>> { + if let Some(tx_pool_waiting) = self.waiting.get(&shard) { + return hashes + .iter() + .map(|h| tx_pool_waiting.get(h).map(|x| x.operation.clone())) + .collect() + } + vec![] + } + + /// Satisfies provided tags in operations that are waiting for them. + /// + /// Returns (and removes) operations that became ready after their last tag got + /// satisfied and now we can remove them from Future and move to Ready queue. + pub fn satisfy_tags>( + &mut self, + tags: impl IntoIterator, + shard: ShardIdentifier, + ) -> Vec> { + let mut became_ready = vec![]; + + for tag in tags { + if let Some(tx_pool_wanted) = self.wanted_tags.get_mut(&shard) { + if let Some(hashes) = tx_pool_wanted.remove(tag.as_ref()) { + if let Some(tx_pool_waiting) = self.waiting.get_mut(&shard) { + for hash in hashes { + let is_ready = { + let tx = tx_pool_waiting.get_mut(&hash).expect(WAITING_PROOF); + tx.satisfy_tag(tag.as_ref()); + tx.is_ready() + }; + + if is_ready { + let tx = tx_pool_waiting.remove(&hash).expect(WAITING_PROOF); + became_ready.push(tx); + } + } + } + } + } + } + + became_ready + } + + /// Removes operations for given list of hashes. + /// + /// Returns a list of actually removed operations. + pub fn remove( + &mut self, + hashes: &[Hash], + shard: ShardIdentifier, + ) -> Vec>> { + let mut removed = vec![]; + if let Some(tx_pool_waiting) = self.waiting.get_mut(&shard) { + if let Some(tx_pool_wanted) = self.wanted_tags.get_mut(&shard) { + for hash in hashes { + if let Some(waiting_tx) = tx_pool_waiting.remove(hash) { + // remove from wanted_tags as well + for tag in waiting_tx.missing_tags { + let remove = if let Some(wanted) = tx_pool_wanted.get_mut(&tag) { + wanted.remove(hash); + wanted.is_empty() + } else { + false + }; + if remove { + tx_pool_wanted.remove(&tag); + } + } + // add to result + removed.push(waiting_tx.operation) + } + } + } + } + removed + } + + /// Fold a list of future operations to compute a single value. + pub fn fold, &WaitingTrustedOperations) -> Option>( + &mut self, + f: F, + shard: ShardIdentifier, + ) -> Option { + if let Some(tx_pool) = self.waiting.get(&shard) { + return tx_pool.values().fold(None, f) + } + None + } + + /// Returns iterator over all future operations + pub fn all( + &self, + shard: ShardIdentifier, + ) -> Box> + '_> { + if let Some(tx_pool) = self.waiting.get(&shard) { + return Box::new(tx_pool.values().map(|waiting| &*waiting.operation)) + } + Box::new(core::iter::empty()) + } + + /// Removes and returns all future operations. + pub fn clear(&mut self, shard: ShardIdentifier) -> Vec>> { + if let Some(wanted_tx_pool) = self.wanted_tags.get_mut(&shard) { + wanted_tx_pool.clear(); + return self + .waiting + .get_mut(&shard) + .unwrap() + .drain() + .map(|(_, tx)| tx.operation) + .collect() + } + vec![] + } + + /// Returns number of operations in the Future queue. + pub fn len(&self, shard: ShardIdentifier) -> usize { + if let Some(tx_pool) = self.waiting.get(&shard) { + return tx_pool.len() + } + 0 + } + + /// Returns sum of encoding lengths of all operations in this queue. + pub fn bytes(&self, shard: ShardIdentifier) -> usize { + if let Some(tx_pool) = self.waiting.get(&shard) { + return tx_pool.values().fold(0, |acc, tx| acc + tx.operation.bytes) + } + 0 + } +} diff --git a/tee-worker/core-primitives/top-pool/src/lib.rs b/tee-worker/core-primitives/top-pool/src/lib.rs new file mode 100644 index 0000000000..740c636305 --- /dev/null +++ b/tee-worker/core-primitives/top-pool/src/lib.rs @@ -0,0 +1,48 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +#![cfg_attr(not(feature = "std"), no_std)] + +#[cfg(all(feature = "std", feature = "sgx"))] +compile_error!("feature \"std\" and feature \"sgx\" cannot be enabled at the same time"); + +#[cfg(all(not(feature = "std"), feature = "sgx"))] +extern crate sgx_tstd as std; + +// re-export module to properly feature gate sgx and regular std environment +#[cfg(all(not(feature = "std"), feature = "sgx"))] +pub mod sgx_reexport_prelude { + pub use jsonrpc_core_sgx as jsonrpc_core; + pub use linked_hash_map_sgx as linked_hash_map; + pub use thiserror_sgx as thiserror; +} + +pub mod base_pool; +pub mod basic_pool; +pub mod error; +pub mod future; +pub mod listener; +pub mod pool; +pub mod primitives; +pub mod ready; +pub mod rotator; +pub mod tracked_map; +pub mod validated_pool; +pub mod watcher; + +#[cfg(any(test, feature = "mocks"))] +pub mod mocks; diff --git a/tee-worker/core-primitives/top-pool/src/listener.rs b/tee-worker/core-primitives/top-pool/src/listener.rs new file mode 100644 index 0000000000..2019e7cb98 --- /dev/null +++ b/tee-worker/core-primitives/top-pool/src/listener.rs @@ -0,0 +1,157 @@ +// This file is part of Substrate. + +// Copyright (C) 2018-2020 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#[cfg(all(not(feature = "std"), feature = "sgx"))] +use crate::sgx_reexport_prelude::*; + +use crate::watcher::Watcher; +use codec::Encode; +use itc_direct_rpc_server::SendRpcResponse; +use itp_types::BlockHash as SidechainBlockHash; +use linked_hash_map::LinkedHashMap; +use log::{debug, trace}; +use sp_runtime::traits; +use std::{collections::HashMap, hash, string::String, sync::Arc, vec, vec::Vec}; + +/// Extrinsic pool default listener. +#[derive(Default)] +pub struct Listener +where + H: hash::Hash + Eq, + R: SendRpcResponse, +{ + watchers: HashMap>, + finality_watchers: LinkedHashMap>, + rpc_response_sender: Arc, +} + +/// Maximum number of blocks awaiting finality at any time. +const MAX_FINALITY_WATCHERS: usize = 512; + +impl Listener +where + H: hash::Hash + traits::Member + Encode, + R: SendRpcResponse, +{ + pub fn new(rpc_response_sender: Arc) -> Self { + Listener { + watchers: Default::default(), + finality_watchers: Default::default(), + rpc_response_sender, + } + } + + fn fire(&mut self, hash: &H, fun: F) + where + F: FnOnce(&mut Watcher), + { + let clean = if let Some(h) = self.watchers.get_mut(hash) { + fun(h); + h.is_done() + } else { + false + }; + + if clean { + self.watchers.remove(hash); + } + } + + /// Creates a new watcher for given verified extrinsic. + /// + /// The watcher can be used to subscribe to life-cycle events of that extrinsic. + pub fn create_watcher(&mut self, hash: H) { + let new_watcher = Watcher::new_watcher(hash.clone(), self.rpc_response_sender.clone()); + self.watchers.insert(hash, new_watcher); + } + + /// Notify the listeners about extrinsic broadcast. + pub fn broadcasted(&mut self, hash: &H, peers: Vec) { + trace!(target: "txpool", "[{:?}] Broadcasted", hash); + self.fire(hash, |watcher| watcher.broadcast(peers)); + } + + /// New operation was added to the ready pool or promoted from the future pool. + pub fn ready(&mut self, tx: &H, old: Option<&H>) { + trace!(target: "txpool", "[{:?}] Ready (replaced with {:?})", tx, old); + self.fire(tx, |watcher| watcher.ready()); + if let Some(old) = old { + self.fire(old, |watcher| watcher.usurped()); + } + } + + /// New operation was added to the future pool. + pub fn future(&mut self, tx: &H) { + trace!(target: "txpool", "[{:?}] Future", tx); + self.fire(tx, |watcher| watcher.future()); + } + + /// TrustedOperation was dropped from the pool because of the limit. + pub fn dropped(&mut self, tx: &H, by: Option<&H>) { + trace!(target: "txpool", "[{:?}] Dropped (replaced with {:?})", tx, by); + self.fire(tx, |watcher| match by { + Some(_) => watcher.usurped(), + None => watcher.dropped(), + }) + } + + /// TrustedOperation was removed as invalid. + pub fn invalid(&mut self, tx: &H) { + self.fire(tx, |watcher| watcher.invalid()); + } + + /// TrustedOperation was pruned from the pool. + #[allow(clippy::or_fun_call)] + pub fn pruned(&mut self, block_hash: SidechainBlockHash, tx: &H) { + debug!(target: "txpool", "[{:?}] Pruned at {:?}", tx, block_hash); + self.fire(tx, |s| s.in_block(block_hash)); + self.finality_watchers.entry(block_hash).or_insert(vec![]).push(tx.clone()); + + while self.finality_watchers.len() > MAX_FINALITY_WATCHERS { + if let Some((_hash, txs)) = self.finality_watchers.pop_front() { + for tx in txs { + self.fire(&tx, |s| s.finality_timeout()); + } + } + } + } + + /// TrustedOperation in block. + pub fn in_block(&mut self, tx: &H, block_hash: SidechainBlockHash) { + self.fire(tx, |s| s.in_block(block_hash)); + } + + /// The block this operation was included in has been retracted. + pub fn retracted(&mut self, block_hash: SidechainBlockHash) { + if let Some(hashes) = self.finality_watchers.remove(&block_hash) { + for hash in hashes { + self.fire(&hash, |s| s.retracted()) + } + } + } + + /// Notify all watchers that operations have been finalized + pub fn finalized(&mut self, block_hash: SidechainBlockHash) { + if let Some(hashes) = self.finality_watchers.remove(&block_hash) { + for hash in hashes { + log::debug!(target: "txpool", "[{:?}] Sent finalization event (block {:?})", hash, block_hash); + self.fire(&hash, |s| s.finalized()) + } + } + } +} diff --git a/tee-worker/core-primitives/top-pool/src/mocks/mod.rs b/tee-worker/core-primitives/top-pool/src/mocks/mod.rs new file mode 100644 index 0000000000..81b1c65ebe --- /dev/null +++ b/tee-worker/core-primitives/top-pool/src/mocks/mod.rs @@ -0,0 +1,22 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +#[cfg(any(test, feature = "mocks"))] +pub mod rpc_responder_mock; + +#[cfg(feature = "mocks")] +pub mod trusted_operation_pool_mock; diff --git a/tee-worker/core-primitives/top-pool/src/mocks/rpc_responder_mock.rs b/tee-worker/core-primitives/top-pool/src/mocks/rpc_responder_mock.rs new file mode 100644 index 0000000000..89f0fac805 --- /dev/null +++ b/tee-worker/core-primitives/top-pool/src/mocks/rpc_responder_mock.rs @@ -0,0 +1,55 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +use itc_direct_rpc_server::{DirectRpcResult, RpcHash, SendRpcResponse}; +use itp_types::TrustedOperationStatus; +use std::{marker::PhantomData, vec::Vec}; + +pub struct RpcResponderMock { + _hash: PhantomData, +} + +impl RpcResponderMock { + pub fn new() -> Self { + RpcResponderMock { _hash: PhantomData } + } +} + +impl Default for RpcResponderMock { + fn default() -> Self { + Self::new() + } +} + +impl SendRpcResponse for RpcResponderMock +where + Hash: RpcHash, +{ + type Hash = Hash; + + fn update_status_event( + &self, + _hash: Self::Hash, + _status_update: TrustedOperationStatus, + ) -> DirectRpcResult<()> { + Ok(()) + } + + fn send_state(&self, _hash: Self::Hash, _state_encoded: Vec) -> DirectRpcResult<()> { + Ok(()) + } +} diff --git a/tee-worker/core-primitives/top-pool/src/mocks/trusted_operation_pool_mock.rs b/tee-worker/core-primitives/top-pool/src/mocks/trusted_operation_pool_mock.rs new file mode 100644 index 0000000000..42956b4b86 --- /dev/null +++ b/tee-worker/core-primitives/top-pool/src/mocks/trusted_operation_pool_mock.rs @@ -0,0 +1,223 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + Copyright (C) 2017-2019 Baidu, Inc. All Rights Reserved. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +#[cfg(all(not(feature = "std"), feature = "sgx"))] +use crate::sgx_reexport_prelude::*; + +#[cfg(feature = "sgx")] +use std::sync::SgxRwLock as RwLock; + +#[cfg(feature = "std")] +use std::sync::RwLock; + +use crate::{ + base_pool::TrustedOperation, + error::Error, + primitives::{ + ImportNotificationStream, PoolFuture, PoolStatus, TrustedOperationPool, + TrustedOperationSource, TxHash, + }, +}; +use codec::Encode; +use core::{future::Future, pin::Pin}; +use ita_stf::TrustedOperation as StfTrustedOperation; +use itp_types::{Block, BlockHash as SidechainBlockHash, ShardIdentifier, H256}; +use jsonrpc_core::futures::future::ready; +use sp_runtime::{ + generic::BlockId, + traits::{BlakeTwo256, Hash, NumberFor}, +}; +use std::{boxed::Box, collections::HashMap, string::String, sync::Arc, vec, vec::Vec}; + +/// Mock for the trusted operation pool +/// +/// To be used in unit tests +pub struct TrustedOperationPoolMock { + submitted_transactions: RwLock>, +} + +/// Transaction payload +#[derive(Clone, PartialEq)] +pub struct TxPayload { + pub block_id: BlockId<::Block>, + pub source: TrustedOperationSource, + pub xts: Vec, + pub shard: ShardIdentifier, +} + +impl Default for TrustedOperationPoolMock { + fn default() -> Self { + TrustedOperationPoolMock { submitted_transactions: RwLock::new(HashMap::new()) } + } +} + +impl TrustedOperationPoolMock { + pub fn get_last_submitted_transactions(&self) -> HashMap { + let transactions = self.submitted_transactions.read().unwrap(); + transactions.clone() + } + + fn map_stf_top_to_tx( + stf_top: &StfTrustedOperation, + ) -> Arc, StfTrustedOperation>> { + Arc::new(TrustedOperation::, StfTrustedOperation> { + data: stf_top.clone(), + bytes: 0, + hash: hash_of_top(stf_top), + priority: 0u64, + valid_till: 0u64, + requires: vec![], + provides: vec![], + propagate: false, + source: TrustedOperationSource::External, + }) + } +} + +impl TrustedOperationPool for TrustedOperationPoolMock { + type Block = Block; + type Hash = H256; + type InPoolOperation = TrustedOperation, StfTrustedOperation>; + type Error = Error; + + #[allow(clippy::type_complexity)] + fn submit_at( + &self, + at: &BlockId, + source: TrustedOperationSource, + xts: Vec, + shard: ShardIdentifier, + ) -> PoolFuture, Self::Error>>, Self::Error> { + let mut transactions = self.submitted_transactions.write().unwrap(); + transactions.insert(shard, TxPayload { block_id: *at, source, xts: xts.clone(), shard }); + + let top_hashes: Vec, Self::Error>> = + xts.iter().map(|top| Ok(hash_of_top(top))).collect(); + + Box::pin(ready(Ok(top_hashes))) + } + + fn submit_one( + &self, + at: &BlockId, + source: TrustedOperationSource, + xt: StfTrustedOperation, + shard: ShardIdentifier, + ) -> PoolFuture, Self::Error> { + let mut transactions = self.submitted_transactions.write().unwrap(); + transactions + .insert(shard, TxPayload { block_id: *at, source, xts: vec![xt.clone()], shard }); + + let top_hash = hash_of_top(&xt); + + Box::pin(ready(Ok(top_hash))) + } + + fn submit_and_watch( + &self, + at: &BlockId, + source: TrustedOperationSource, + xt: StfTrustedOperation, + shard: ShardIdentifier, + ) -> PoolFuture, Self::Error> { + self.submit_one(at, source, xt, shard) + } + + #[allow(clippy::type_complexity)] + fn ready_at( + &self, + _at: NumberFor, + _shard: ShardIdentifier, + ) -> Pin< + Box< + dyn Future> + Send>> + Send, + >, + > { + unimplemented!() + } + + #[allow(clippy::type_complexity)] + fn ready( + &self, + shard: ShardIdentifier, + ) -> Box> + Send> { + let transactions = self.submitted_transactions.read().unwrap(); + let ready_transactions = transactions + .get(&shard) + .map(|payload| payload.xts.iter().map(Self::map_stf_top_to_tx).collect()) + .unwrap_or_else(Vec::new); + Box::new(ready_transactions.into_iter()) + } + + fn shards(&self) -> Vec { + let transactions = self.submitted_transactions.read().unwrap(); + transactions.iter().map(|(shard, _)| *shard).collect() + } + + fn remove_invalid( + &self, + _hashes: &[TxHash], + _shard: ShardIdentifier, + _inblock: bool, + ) -> Vec> { + Vec::new() + } + + fn status(&self, shard: ShardIdentifier) -> PoolStatus { + let transactions = self.submitted_transactions.read().unwrap(); + transactions + .get(&shard) + .map(|payload| PoolStatus { + ready: payload.xts.len(), + ready_bytes: 0, + future: 0, + future_bytes: 0, + }) + .unwrap_or_else(default_pool_status) + } + + fn import_notification_stream(&self) -> ImportNotificationStream> { + unimplemented!() + } + + fn on_broadcasted(&self, _propagations: HashMap, Vec>) { + unimplemented!() + } + + fn hash_of(&self, xt: &StfTrustedOperation) -> TxHash { + hash_of_top(xt) + } + + fn ready_transaction( + &self, + _hash: &TxHash, + _shard: ShardIdentifier, + ) -> Option> { + unimplemented!() + } + + fn on_block_imported(&self, _hashes: &[Self::Hash], _block_hash: SidechainBlockHash) {} +} + +fn default_pool_status() -> PoolStatus { + PoolStatus { ready: 0, ready_bytes: 0, future: 0, future_bytes: 0 } +} + +fn hash_of_top(top: &StfTrustedOperation) -> H256 { + top.using_encoded(|x| BlakeTwo256::hash(x)) +} diff --git a/tee-worker/core-primitives/top-pool/src/pool.rs b/tee-worker/core-primitives/top-pool/src/pool.rs new file mode 100644 index 0000000000..c1955806ef --- /dev/null +++ b/tee-worker/core-primitives/top-pool/src/pool.rs @@ -0,0 +1,970 @@ +// This file is part of Substrate. + +// Copyright (C) 2018-2020 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#[cfg(all(not(feature = "std"), feature = "sgx"))] +use crate::sgx_reexport_prelude::*; + +use crate::{ + base_pool as base, error, + primitives::TrustedOperationSource, + validated_pool::{ValidatedOperation, ValidatedPool}, +}; +use core::matches; +use ita_stf::{ShardIdentifier, TrustedOperation as StfTrustedOperation}; +use itc_direct_rpc_server::SendRpcResponse; +use itp_types::BlockHash as SidechainBlockHash; +use jsonrpc_core::futures::{channel::mpsc::Receiver, future, Future}; +use sp_runtime::{ + generic::BlockId, + traits::{self, Block as BlockT, SaturatedConversion}, + transaction_validity::{TransactionTag as Tag, TransactionValidity, TransactionValidityError}, +}; +use std::{collections::HashMap, format, sync::Arc, time::Instant, vec::Vec}; + +/// Modification notification event stream type; +pub type EventStream = Receiver; + +/// Block hash type for a pool. +pub type BlockHash = <::Block as traits::Block>::Hash; +/// Extrinsic hash type for a pool. +pub type ExtrinsicHash = <::Block as traits::Block>::Hash; +/// Extrinsic type for a pool. +//pub type ExtrinsicFor = <::Block as traits::Block>::Extrinsic; +/// Block number type for the ChainApi +pub type NumberFor = traits::NumberFor<::Block>; +/// A type of operation stored in the pool +pub type TransactionFor = Arc, StfTrustedOperation>>; +/// A type of validated operation stored in the pool. +pub type ValidatedOperationFor = + ValidatedOperation, StfTrustedOperation, ::Error>; + +/// Concrete extrinsic validation and query logic. +pub trait ChainApi: Send + Sync { + /// Block type. + type Block: BlockT; + /// Error type. + type Error: From; + /// Validate operation future. + type ValidationFuture: Future> + Send + Unpin; + /// Body future (since block body might be remote) + type BodyFuture: Future>, Self::Error>> + + Unpin + + Send + + 'static; + + /// Verify extrinsic at given block. + fn validate_transaction( + &self, + source: TrustedOperationSource, + uxt: StfTrustedOperation, + shard: ShardIdentifier, + ) -> Self::ValidationFuture; + + /// Returns a block number given the block id. + fn block_id_to_number( + &self, + at: &BlockId, + ) -> Result>, Self::Error>; + + /// Returns a block hash given the block id. + fn block_id_to_hash( + &self, + at: &BlockId, + ) -> Result, Self::Error>; + + /// Returns hash and encoding length of the extrinsic. + fn hash_and_length(&self, uxt: &StfTrustedOperation) -> (ExtrinsicHash, usize); + + /// Returns a block body given the block id. + fn block_body(&self, at: &BlockId) -> Self::BodyFuture; +} + +/// Pool configuration options. +#[derive(Debug, Clone)] +pub struct Options { + /// Ready queue limits. + pub ready: base::Limit, + /// Future queue limits. + pub future: base::Limit, + /// Reject future operations. + pub reject_future_operations: bool, +} + +impl Default for Options { + fn default() -> Self { + Options { + ready: base::Limit { count: 8192, total_bytes: 20 * 1024 * 1024 }, + future: base::Limit { count: 512, total_bytes: 1024 * 1024 }, + reject_future_operations: false, + } + } +} + +/// Should we check that the operation is banned +/// in the pool, before we verify it? +#[derive(Copy, Clone)] +enum CheckBannedBeforeVerify { + Yes, + No, +} + +/// Extrinsics pool that performs validation. +pub struct Pool +where + R: SendRpcResponse>, +{ + validated_pool: Arc>, +} + +impl Pool +where + //<::Block as sp_runtime::traits::Block>::Hash: Serialize, + ::Error: error::IntoPoolError, + R: SendRpcResponse>, +{ + /// Create a new operation pool. + pub fn new(options: Options, api: Arc, rpc_response_sender: Arc) -> Self { + Pool { validated_pool: Arc::new(ValidatedPool::new(options, api, rpc_response_sender)) } + } + + /// Imports a bunch of unverified extrinsics to the pool + pub async fn submit_at( + &self, + at: &BlockId, + source: TrustedOperationSource, + xts: impl IntoIterator, + shard: ShardIdentifier, + ) -> Result, B::Error>>, B::Error> { + let xts = xts.into_iter().map(|xt| (source, xt)); + let validated_transactions = + self.verify(at, xts, CheckBannedBeforeVerify::Yes, shard).await?; + Ok(self.validated_pool.submit(validated_transactions.into_values(), shard)) + } + + /// Resubmit the given extrinsics to the pool. + /// + /// This does not check if a operation is banned, before we verify it again. + pub async fn resubmit_at( + &self, + at: &BlockId, + source: TrustedOperationSource, + xts: impl IntoIterator, + shard: ShardIdentifier, + ) -> Result, B::Error>>, B::Error> { + let xts = xts.into_iter().map(|xt| (source, xt)); + let validated_transactions = + self.verify(at, xts, CheckBannedBeforeVerify::No, shard).await?; + Ok(self.validated_pool.submit(validated_transactions.into_values(), shard)) + } + + /// Imports one unverified extrinsic to the pool + pub async fn submit_one( + &self, + at: &BlockId, + source: TrustedOperationSource, + xt: StfTrustedOperation, + shard: ShardIdentifier, + ) -> Result, B::Error> { + let res = self.submit_at(at, source, std::iter::once(xt), shard).await?.pop(); + res.expect("One extrinsic passed; one result returned; qed") + } + + /// Import a single extrinsic and starts to watch their progress in the pool. + pub async fn submit_and_watch( + &self, + at: &BlockId, + source: TrustedOperationSource, + xt: StfTrustedOperation, + shard: ShardIdentifier, + ) -> Result, B::Error> { + //TODO + //let block_number = self.resolve_block_number(at)?; + // dummy value: + let block_number = 0; + let (_, tx) = self + .verify_one(at, block_number, source, xt, CheckBannedBeforeVerify::Yes, shard) + .await; + self.validated_pool.submit_and_watch(tx, shard) + } + + /// Resubmit some operation that were validated elsewhere. + pub fn resubmit( + &self, + revalidated_transactions: HashMap, ValidatedOperationFor>, + shard: ShardIdentifier, + ) { + let now = Instant::now(); + self.validated_pool.resubmit(revalidated_transactions, shard); + log::debug!(target: "txpool", + "Resubmitted. Took {} ms. Status: {:?}", + now.elapsed().as_millis(), + self.validated_pool.status(shard) + ); + } + + /// Prunes known ready operations. + /// + /// Used to clear the pool from operations that were part of recently imported block. + /// The main difference from the `prune` is that we do not revalidate any operations + /// and ignore unknown passed hashes. + pub fn prune_known( + &self, + at: &BlockId, + hashes: &[ExtrinsicHash], + shard: ShardIdentifier, + ) -> Result<(), B::Error> { + // Get details of all extrinsics that are already in the pool + #[allow(clippy::filter_map_identity)] + // false positive. Filter map does filter because x is an option + let in_pool_tags = self + .validated_pool + .extrinsics_tags(hashes, shard) + .into_iter() + .filter_map(|x| x) + .flatten(); + + // Prune all operations that provide given tags + let prune_status = self.validated_pool.prune_tags(in_pool_tags, shard)?; + let pruned_transactions = + hashes.iter().cloned().chain(prune_status.pruned.iter().map(|tx| tx.hash)); + self.validated_pool.fire_pruned(at, pruned_transactions) + } + + /// Prunes ready operations. + /// + /// Used to clear the pool from operations that were part of recently imported block. + /// To perform pruning we need the tags that each extrinsic provides and to avoid calling + /// into runtime too often we first lookup all extrinsics that are in the pool and get + /// their provided tags from there. Otherwise we query the runtime at the `parent` block. + pub async fn prune( + &self, + at: &BlockId, + _parent: &BlockId, + extrinsics: &[StfTrustedOperation], + shard: ShardIdentifier, + ) -> Result<(), B::Error> { + log::debug!( + target: "txpool", + "Starting pruning of block {:?} (extrinsics: {})", + at, + extrinsics.len() + ); + // Get details of all extrinsics that are already in the pool + let in_pool_hashes = + extrinsics.iter().map(|extrinsic| self.hash_of(extrinsic)).collect::>(); + let in_pool_tags = self.validated_pool.extrinsics_tags(&in_pool_hashes, shard); + + // Zip the ones from the pool with the full list (we get pairs `(Extrinsic, Option>)`) + let all = extrinsics.iter().zip(in_pool_tags.into_iter()); + + let mut future_tags = Vec::new(); + for (extrinsic, in_pool_tags) in all { + match in_pool_tags { + // reuse the tags for extrinsics that were found in the pool + Some(tags) => future_tags.extend(tags), + // if it's not found in the pool query the runtime at parent block + // to get validity info and tags that the extrinsic provides. + None => { + let validity = self + .validated_pool + .api() + .validate_transaction( + TrustedOperationSource::InBlock, + extrinsic.clone(), + shard, + ) + .await; + + if let Ok(Ok(validity)) = validity { + future_tags.extend(validity.provides); + } + }, + } + } + + self.prune_tags(at, future_tags, in_pool_hashes, shard).await + } + + /// Prunes ready operations that provide given list of tags. + /// + /// Given tags are assumed to be always provided now, so all operations + /// in the Future Queue that require that particular tag (and have other + /// requirements satisfied) are promoted to Ready Queue. + /// + /// Moreover for each provided tag we remove operations in the pool that: + /// 1. Provide that tag directly + /// 2. Are a dependency of pruned operation. + /// + /// Returns operations that have been removed from the pool and must be reverified + /// before reinserting to the pool. + /// + /// By removing predecessor operations as well we might actually end up + /// pruning too much, so all removed operations are reverified against + /// the runtime (`validate_transaction`) to make sure they are invalid. + /// + /// However we avoid revalidating operations that are contained within + /// the second parameter of `known_imported_hashes`. These operations + /// (if pruned) are not revalidated and become temporarily banned to + /// prevent importing them in the (near) future. + pub async fn prune_tags( + &self, + at: &BlockId, + tags: impl IntoIterator, + known_imported_hashes: impl IntoIterator> + Clone, + shard: ShardIdentifier, + ) -> Result<(), B::Error> { + log::debug!(target: "txpool", "Pruning at {:?}", at); + // Prune all operations that provide given tags + let prune_status = match self.validated_pool.prune_tags(tags, shard) { + Ok(prune_status) => prune_status, + Err(e) => return Err(e), + }; + + // Make sure that we don't revalidate extrinsics that were part of the recently + // imported block. This is especially important for UTXO-like chains cause the + // inputs are pruned so such operation would go to future again. + self.validated_pool + .ban(&Instant::now(), known_imported_hashes.clone().into_iter()); + + // Try to re-validate pruned operations since some of them might be still valid. + // note that `known_imported_hashes` will be rejected here due to temporary ban. + let pruned_hashes = prune_status.pruned.iter().map(|tx| tx.hash).collect::>(); + let pruned_transactions = + prune_status.pruned.into_iter().map(|tx| (tx.source, tx.data.clone())); + + let reverified_transactions = self + .verify(at, pruned_transactions, CheckBannedBeforeVerify::Yes, shard) + .await?; + + log::trace!(target: "txpool", "Pruning at {:?}. Resubmitting operations.", at); + // And finally - submit reverified operations back to the pool + + self.validated_pool.resubmit_pruned( + at, + known_imported_hashes, + pruned_hashes, + reverified_transactions.into_values().collect(), + shard, + ) + } + + /// Returns operation hash + pub fn hash_of(&self, xt: &StfTrustedOperation) -> ExtrinsicHash { + self.validated_pool.api().hash_and_length(xt).0 + } + + /// Resolves block number by id. + fn _resolve_block_number(&self, at: &BlockId) -> Result, B::Error> { + self.validated_pool.api().block_id_to_number(at).and_then(|number| { + number.ok_or_else(|| error::Error::InvalidBlockId(format!("{:?}", at)).into()) + }) + } + + /// Returns future that validates a bunch of operations at given block. + async fn verify( + &self, + at: &BlockId, + xts: impl IntoIterator, + check: CheckBannedBeforeVerify, + shard: ShardIdentifier, + ) -> Result, ValidatedOperationFor>, B::Error> { + //FIXME: Nicer verify + // we need a block number to compute tx validity + //let block_number = self.resolve_block_number(at)?; + // dummy blocknumber + //pub type NumberFor = traits::NumberFor<::Block>; + let block_number = 0; + + let res = future::join_all( + xts.into_iter() + .map(|(source, xt)| self.verify_one(at, block_number, source, xt, check, shard)), + ) + .await + .into_iter() + .collect::>(); + + Ok(res) + } + + /// Returns future that validates single operation at given block. + async fn verify_one( + &self, + _block_id: &BlockId, + //block_number: NumberFor, + block_number: i8, + source: TrustedOperationSource, + xt: StfTrustedOperation, + check: CheckBannedBeforeVerify, + shard: ShardIdentifier, + ) -> (ExtrinsicHash, ValidatedOperationFor) { + let (hash, bytes) = self.validated_pool.api().hash_and_length(&xt); + + let ignore_banned = matches!(check, CheckBannedBeforeVerify::No); + if let Err(err) = self.validated_pool.check_is_known(&hash, ignore_banned, shard) { + return (hash, ValidatedOperation::Invalid(hash, err)) + } + + //FIXME: + // no runtime validation check for now. + let validation_result = + self.validated_pool.api().validate_transaction(source, xt.clone(), shard).await; + + let status = match validation_result { + Ok(status) => status, + Err(e) => return (hash, ValidatedOperation::Invalid(hash, e)), + }; + + let validity = match status { + Ok(validity) => + if validity.provides.is_empty() { + ValidatedOperation::Invalid(hash, error::Error::NoTagsProvided.into()) + } else { + ValidatedOperation::valid_at( + block_number.saturated_into::(), + hash, + source, + xt, + bytes, + validity, + ) + }, + Err(TransactionValidityError::Invalid(_e)) => + ValidatedOperation::Invalid(hash, error::Error::InvalidTrustedOperation.into()), + Err(TransactionValidityError::Unknown(_e)) => + ValidatedOperation::Unknown(hash, error::Error::UnknownTrustedOperation.into()), + }; + + (hash, validity) + } + + /// get a reference to the underlying validated pool. + pub fn validated_pool(&self) -> &ValidatedPool { + &self.validated_pool + } +} + +impl Clone for Pool +where + ::Error: error::IntoPoolError, + R: SendRpcResponse>, +{ + fn clone(&self) -> Self { + Self { validated_pool: self.validated_pool.clone() } + } +} + +#[cfg(test)] +pub mod tests { + + use super::*; + use crate::{ + base_pool::Limit, mocks::rpc_responder_mock::RpcResponderMock, + primitives::from_low_u64_to_be_h256, + }; + use codec::{Decode, Encode}; + use ita_stf::{Index, TrustedCall, TrustedCallSigned, TrustedOperation}; + use itp_types::Header; + use jsonrpc_core::{futures, futures::executor::block_on}; + use parity_util_mem::MallocSizeOf; + use serde::Serialize; + use sp_application_crypto::ed25519; + use sp_core::{hash::H256, Pair}; + use sp_runtime::{ + traits::{BlakeTwo256, Extrinsic as ExtrinsicT, Hash, Verify}, + transaction_validity::{InvalidTransaction as InvalidTrustedOperation, ValidTransaction}, + MultiSignature, + }; + use std::{collections::HashSet, sync::Mutex}; + + #[derive(Clone, PartialEq, Eq, Encode, Decode, core::fmt::Debug, Serialize, MallocSizeOf)] + pub enum Extrinsic { + IncludeData(Vec), + StorageChange(Vec, Option>), + OffchainIndexSet(Vec, Vec), + OffchainIndexClear(Vec), + } + + impl ExtrinsicT for Extrinsic { + type Call = Extrinsic; + type SignaturePayload = (); + + fn is_signed(&self) -> Option { + if let Extrinsic::IncludeData(_) = *self { + Some(false) + } else { + Some(true) + } + } + + fn new( + call: Self::Call, + _signature_payload: Option, + ) -> Option { + Some(call) + } + } + + /// The signature type used by accounts/transactions. + pub type AccountSignature = ed25519::Signature; + /// An identifier for an account on this system. + pub type AccountId = ::Signer; + /// The hashing algorithm used. + pub type Hashing = BlakeTwo256; + /// The item of a block digest. + pub type DigestItem = sp_runtime::generic::DigestItem; + /// The digest of a block. + pub type Digest = sp_runtime::generic::Digest; + /// A test block. + pub type Block = sp_runtime::generic::Block; + /// Test RPC responder + pub type TestRpcResponder = RpcResponderMock; + + const INVALID_NONCE: Index = 254; + const SOURCE: TrustedOperationSource = TrustedOperationSource::External; + + #[derive(Clone, Debug, Default)] + struct TestApi { + delay: Arc>>>, + invalidate: Arc>>, + clear_requirements: Arc>>, + add_requirements: Arc>>, + } + + impl ChainApi for TestApi { + type Block = tests::Block; + type Error = error::Error; + type ValidationFuture = futures::future::Ready>; + type BodyFuture = futures::future::Ready>>>; + + /// Verify extrinsic at given block. + fn validate_transaction( + &self, + _source: TrustedOperationSource, + uxt: StfTrustedOperation, + _shard: ShardIdentifier, + ) -> Self::ValidationFuture { + let hash = self.hash_and_length(&uxt).0; + let nonce: Index = match uxt { + StfTrustedOperation::direct_call(signed_call) => signed_call.nonce, + _ => 0, + }; + + // This is used to control the test flow. + if nonce > 0 { + let opt = self.delay.lock().unwrap().take(); + if let Some(delay) = opt { + if delay.recv().is_err() { + println!("Error waiting for delay!"); + } + } + } + + if self.invalidate.lock().unwrap().contains(&hash) { + return futures::future::ready(Ok(InvalidTrustedOperation::Custom(0).into())) + } + + futures::future::ready(if nonce > 254 { + Ok(InvalidTrustedOperation::Stale.into()) + } else { + let mut operation = ValidTransaction { + priority: 4, + requires: if nonce > 0 { vec![vec![nonce as u8 - 1]] } else { vec![] }, + provides: if nonce == INVALID_NONCE { vec![] } else { vec![vec![nonce as u8]] }, + longevity: 3, + propagate: true, + }; + + if self.clear_requirements.lock().unwrap().contains(&hash) { + operation.requires.clear(); + } + + if self.add_requirements.lock().unwrap().contains(&hash) { + operation.requires.push(vec![128]); + } + + Ok(Ok(operation)) + }) + } + + /// Returns a block number given the block id. + fn block_id_to_number( + &self, + at: &BlockId, + ) -> Result>, Self::Error> { + Ok(match at { + BlockId::Number(num) => Some(*num), + BlockId::Hash(_) => None, + }) + } + + /// Returns a block hash given the block id. + fn block_id_to_hash( + &self, + at: &BlockId, + ) -> Result, Self::Error> { + Ok(match at { + BlockId::Number(num) => Some(from_low_u64_to_be_h256((*num).into())), + BlockId::Hash(_) => None, + }) + } + + /// Hash the extrinsic. + fn hash_and_length(&self, uxt: &StfTrustedOperation) -> (BlockHash, usize) { + let encoded = uxt.encode(); + let len = encoded.len(); + (tests::Hashing::hash_of(&encoded), len) + } + + fn block_body(&self, _id: &BlockId) -> Self::BodyFuture { + futures::future::ready(Ok(None)) + } + } + + fn to_top(call: TrustedCall, nonce: Index) -> TrustedOperation { + let msg = &b"test-message"[..]; + let (pair, _) = ed25519::Pair::generate(); + + let signature = pair.sign(&msg); + let multi_sig = MultiSignature::from(signature); + TrustedCallSigned::new(call, nonce, multi_sig).into_trusted_operation(true) + } + + fn test_pool() -> Pool> { + Pool::new( + Default::default(), + TestApi::default().into(), + Arc::new(RpcResponderMock::::new()), + ) + } + + #[test] + pub fn test_should_validate_and_import_transaction() { + // given + let pool = test_pool(); + let shard = ShardIdentifier::default(); + + // when + let hash = block_on(pool.submit_one( + &BlockId::Number(0), + SOURCE, + to_top( + TrustedCall::balance_transfer( + tests::AccountId::from_h256(from_low_u64_to_be_h256(1)).into(), + tests::AccountId::from_h256(from_low_u64_to_be_h256(2)).into(), + 5, + ), + 0, + ), + shard, + )) + .unwrap(); + + // then + assert_eq!( + pool.validated_pool().ready(shard).map(|v| v.hash).collect::>(), + vec![hash] + ); + } + + #[test] + pub fn test_should_reject_if_temporarily_banned() { + // given + let pool = test_pool(); + let shard = ShardIdentifier::default(); + let top = to_top( + TrustedCall::balance_transfer( + tests::AccountId::from_h256(from_low_u64_to_be_h256(1)).into(), + tests::AccountId::from_h256(from_low_u64_to_be_h256(2)).into(), + 5, + ), + 0, + ); + + // when + pool.validated_pool.rotator().ban(&Instant::now(), vec![pool.hash_of(&top)]); + let res = block_on(pool.submit_one(&BlockId::Number(0), SOURCE, top, shard)); + assert_eq!(pool.validated_pool().status(shard).ready, 0); + assert_eq!(pool.validated_pool().status(shard).future, 0); + + // then + assert!(matches!(res.unwrap_err(), error::Error::TemporarilyBanned)); + } + + #[test] + pub fn test_should_notify_about_pool_events() { + let (stream, hash0, hash1) = { + // given + let pool = test_pool(); + let shard = ShardIdentifier::default(); + let stream = pool.validated_pool().import_notification_stream(); + + // when + let hash0 = block_on(pool.submit_one( + &BlockId::Number(0), + SOURCE, + to_top( + TrustedCall::balance_transfer( + tests::AccountId::from_h256(from_low_u64_to_be_h256(1)).into(), + tests::AccountId::from_h256(from_low_u64_to_be_h256(2)).into(), + 5, + ), + 0, + ), + shard, + )) + .unwrap(); + let hash1 = block_on(pool.submit_one( + &BlockId::Number(0), + SOURCE, + to_top( + TrustedCall::balance_transfer( + tests::AccountId::from_h256(from_low_u64_to_be_h256(1)).into(), + tests::AccountId::from_h256(from_low_u64_to_be_h256(2)).into(), + 5, + ), + 1, + ), + shard, + )) + .unwrap(); + // future doesn't count + let _hash = block_on(pool.submit_one( + &BlockId::Number(0), + SOURCE, + to_top( + TrustedCall::balance_transfer( + tests::AccountId::from_h256(from_low_u64_to_be_h256(1)).into(), + tests::AccountId::from_h256(from_low_u64_to_be_h256(2)).into(), + 5, + ), + 3, + ), + shard, + )) + .unwrap(); + + assert_eq!(pool.validated_pool().status(shard).ready, 2); + assert_eq!(pool.validated_pool().status(shard).future, 1); + + (stream, hash0, hash1) + }; + + // then + let mut it = futures::executor::block_on_stream(stream); + assert_eq!(it.next(), Some(hash0)); + assert_eq!(it.next(), Some(hash1)); + assert_eq!(it.next(), None); + } + + #[test] + pub fn test_should_clear_stale_transactions() { + // given + let pool = test_pool(); + let shard = ShardIdentifier::default(); + let hash1 = block_on(pool.submit_one( + &BlockId::Number(0), + SOURCE, + to_top( + TrustedCall::balance_transfer( + tests::AccountId::from_h256(from_low_u64_to_be_h256(1)).into(), + tests::AccountId::from_h256(from_low_u64_to_be_h256(2)).into(), + 5, + ), + 0, + ), + shard, + )) + .unwrap(); + let hash2 = block_on(pool.submit_one( + &BlockId::Number(0), + SOURCE, + to_top( + TrustedCall::balance_transfer( + tests::AccountId::from_h256(from_low_u64_to_be_h256(1)).into(), + tests::AccountId::from_h256(from_low_u64_to_be_h256(2)).into(), + 5, + ), + 1, + ), + shard, + )) + .unwrap(); + let hash3 = block_on(pool.submit_one( + &BlockId::Number(0), + SOURCE, + to_top( + TrustedCall::balance_transfer( + tests::AccountId::from_h256(from_low_u64_to_be_h256(1)).into(), + tests::AccountId::from_h256(from_low_u64_to_be_h256(2)).into(), + 5, + ), + 3, + ), + shard, + )) + .unwrap(); + // when + pool.validated_pool.clear_stale(&BlockId::Number(5), shard).unwrap(); + + // then + assert_eq!(pool.validated_pool().ready(shard).count(), 0); + assert_eq!(pool.validated_pool().status(shard).future, 0); + assert_eq!(pool.validated_pool().status(shard).ready, 0); + // make sure they are temporarily banned as well + assert!(pool.validated_pool.rotator().is_banned(&hash1)); + assert!(pool.validated_pool.rotator().is_banned(&hash2)); + assert!(pool.validated_pool.rotator().is_banned(&hash3)); + } + + #[test] + pub fn test_should_ban_mined_transactions() { + // given + let pool = test_pool(); + let shard = ShardIdentifier::default(); + let hash1 = block_on(pool.submit_one( + &BlockId::Number(0), + SOURCE, + to_top( + TrustedCall::balance_transfer( + tests::AccountId::from_h256(from_low_u64_to_be_h256(1)).into(), + tests::AccountId::from_h256(from_low_u64_to_be_h256(2)).into(), + 5, + ), + 0, + ), + shard, + )) + .unwrap(); + + // when + block_on(pool.prune_tags(&BlockId::Number(1), vec![vec![0]], vec![hash1], shard)).unwrap(); + + // then + assert!(pool.validated_pool.rotator().is_banned(&hash1)); + } + + #[test] + #[ignore] // flaky, fails sometimes + pub fn test_should_limit_futures() { + // given + let shard = ShardIdentifier::default(); + let limit = Limit { count: 100, total_bytes: 300 }; + let pool = Pool::new( + Options { ready: limit.clone(), future: limit, ..Default::default() }, + TestApi::default().into(), + Arc::new(TestRpcResponder::new()), + ); + + let hash1 = block_on(pool.submit_one( + &BlockId::Number(0), + SOURCE, + to_top( + TrustedCall::balance_transfer( + tests::AccountId::from_h256(from_low_u64_to_be_h256(1)).into(), + tests::AccountId::from_h256(from_low_u64_to_be_h256(2)).into(), + 5, + ), + 1, + ), + shard, + )) + .unwrap(); + assert_eq!(pool.validated_pool().status(shard).future, 1); + + // when + let hash2 = block_on(pool.submit_one( + &BlockId::Number(0), + SOURCE, + to_top( + TrustedCall::balance_transfer( + tests::AccountId::from_h256(from_low_u64_to_be_h256(2)).into(), + tests::AccountId::from_h256(from_low_u64_to_be_h256(2)).into(), + 5, + ), + 10, + ), + shard, + )) + .unwrap(); + + // then + assert_eq!(pool.validated_pool().status(shard).future, 1); + assert!(pool.validated_pool.rotator().is_banned(&hash1)); + assert!(!pool.validated_pool.rotator().is_banned(&hash2)); + } + + #[test] + pub fn test_should_error_if_reject_immediately() { + // given + let shard = ShardIdentifier::default(); + let limit = Limit { count: 100, total_bytes: 10 }; + let pool = Pool::new( + Options { ready: limit.clone(), future: limit, ..Default::default() }, + TestApi::default().into(), + Arc::new(TestRpcResponder::new()), + ); + + // when + block_on(pool.submit_one( + &BlockId::Number(0), + SOURCE, + to_top( + TrustedCall::balance_transfer( + tests::AccountId::from_h256(from_low_u64_to_be_h256(1)).into(), + tests::AccountId::from_h256(from_low_u64_to_be_h256(2)).into(), + 5, + ), + 1, + ), + shard, + )) + .unwrap_err(); + + // then + assert_eq!(pool.validated_pool().status(shard).ready, 0); + assert_eq!(pool.validated_pool().status(shard).future, 0); + } + + #[test] + pub fn test_should_reject_transactions_with_no_provides() { + // given + let pool = test_pool(); + let shard = ShardIdentifier::default(); + + // when + let err = block_on(pool.submit_one( + &BlockId::Number(0), + SOURCE, + to_top( + TrustedCall::balance_transfer( + tests::AccountId::from_h256(from_low_u64_to_be_h256(1)).into(), + tests::AccountId::from_h256(from_low_u64_to_be_h256(2)).into(), + 5, + ), + INVALID_NONCE, + ), + shard, + )) + .unwrap_err(); + + // then + assert_eq!(pool.validated_pool().status(shard).ready, 0); + assert_eq!(pool.validated_pool().status(shard).future, 0); + assert!(matches!(err, error::Error::NoTagsProvided)); + } +} diff --git a/tee-worker/core-primitives/top-pool/src/primitives.rs b/tee-worker/core-primitives/top-pool/src/primitives.rs new file mode 100644 index 0000000000..4655d86919 --- /dev/null +++ b/tee-worker/core-primitives/top-pool/src/primitives.rs @@ -0,0 +1,347 @@ +// File replacing substrate crate sp_transaction_pool::{error, PoolStatus}; + +#[cfg(all(not(feature = "std"), feature = "sgx"))] +use crate::sgx_reexport_prelude::*; + +extern crate alloc; +use crate::error; +use alloc::{boxed::Box, string::String, sync::Arc, vec::Vec}; +use byteorder::{BigEndian, ByteOrder}; +use codec::{Decode, Encode}; +use core::{hash::Hash, pin::Pin}; +use ita_stf::{ShardIdentifier, TrustedOperation as StfTrustedOperation}; +use itp_types::BlockHash as SidechainBlockHash; +use jsonrpc_core::futures::{channel::mpsc::Receiver, Future, Stream}; +use sp_core::H256; +use sp_runtime::{ + generic::BlockId, + traits::{Block as BlockT, Member, NumberFor}, + transaction_validity::{TransactionLongevity, TransactionPriority, TransactionTag}, +}; +use std::collections::HashMap; + +/// TrustedOperation pool status. +#[derive(Debug)] +pub struct PoolStatus { + /// Number of operations in the ready queue. + pub ready: usize, + /// Sum of bytes of ready operation encodings. + pub ready_bytes: usize, + /// Number of operations in the future queue. + pub future: usize, + /// Sum of bytes of ready operation encodings. + pub future_bytes: usize, +} + +impl PoolStatus { + /// Returns true if the are no operations in the pool. + pub fn is_empty(&self) -> bool { + self.ready == 0 && self.future == 0 + } +} + +/// Possible operation status events. +/// +/// This events are being emitted by `TrustedOperationPool` watchers, +/// which are also exposed over RPC. +/// +/// The status events can be grouped based on their kinds as: +/// 1. Entering/Moving within the pool: +/// - `Future` +/// - `Ready` +/// 2. Inside `Ready` queue: +/// - `Broadcast` +/// 3. Leaving the pool: +/// - `InBlock` +/// - `Invalid` +/// - `Usurped` +/// - `Dropped` +/// 4. Re-entering the pool: +/// - `Retracted` +/// 5. Block finalized: +/// - `Finalized` +/// - `FinalityTimeout` +/// +/// The events will always be received in the order described above, however +/// there might be cases where operations alternate between `Future` and `Ready` +/// pool, and are `Broadcast` in the meantime. +/// +/// There is also only single event causing the operation to leave the pool. +/// I.e. only one of the listed ones should be triggered. +/// +/// Note that there are conditions that may cause operations to reappear in the pool. +/// 1. Due to possible forks, the operation that ends up being in included +/// in one block, may later re-enter the pool or be marked as invalid. +/// 2. TrustedOperation `Dropped` at one point, may later re-enter the pool if some other +/// operations are removed. +/// 3. `Invalid` operation may become valid at some point in the future. +/// (Note that runtimes are encouraged to use `UnknownValidity` to inform the pool about +/// such case). +/// 4. `Retracted` operations might be included in some next block. +/// +/// The stream is considered finished only when either `Finalized` or `FinalityTimeout` +/// event is triggered. You are however free to unsubscribe from notifications at any point. +/// The first one will be emitted when the block, in which operation was included gets +/// finalized. The `FinalityTimeout` event will be emitted when the block did not reach finality +/// within 512 blocks. This either indicates that finality is not available for your chain, +/// or that finality gadget is lagging behind. If you choose to wait for finality longer, you can +/// re-subscribe for a particular operation hash manually again. +#[derive(Debug, Clone, PartialEq)] +pub enum TrustedOperationStatus { + /// TrustedOperation is part of the future queue. + Future, + /// TrustedOperation is part of the ready queue. + Ready, + /// The operation has been broadcast to the given peers. + Broadcast(Vec), + /// TrustedOperation has been included in block with given hash. + InBlock(BlockHash), + /// The block this operation was included in has been retracted. + Retracted(BlockHash), + /// Maximum number of finality watchers has been reached, + /// old watchers are being removed. + FinalityTimeout(BlockHash), + /// TrustedOperation has been finalized by a finality-gadget, e.g GRANDPA + Finalized(BlockHash), + /// TrustedOperation has been replaced in the pool, by another operation + /// that provides the same tags. (e.g. same (sender, nonce)). + Usurped(Hash), + /// TrustedOperation has been dropped from the pool because of the limit. + Dropped, + /// TrustedOperation is no longer valid in the current state. + Invalid, +} + +/// The stream of operation events. +pub type TrustedOperationStatusStream = + dyn Stream> + Send + Unpin; + +/// The import notification event stream. +pub type ImportNotificationStream = Receiver; + +/// TrustedOperation hash type for a pool. +pub type TxHash

=

::Hash; +/// Block hash type for a pool. +pub type BlockHash

= <

::Block as BlockT>::Hash; +/// Type of operations event stream for a pool. +pub type TrustedOperationStatusStreamFor

= TrustedOperationStatusStream, BlockHash

>; + +/// Typical future type used in operation pool api. +pub type PoolFuture = Pin> + Send>>; + +/// In-pool operation interface. +/// +/// The pool is container of operations that are implementing this trait. +/// See `sp_runtime::ValidTransaction` for details about every field. +pub trait InPoolOperation { + /// TrustedOperation type. + type TrustedOperation; + /// TrustedOperation hash type. + type Hash; + + /// Get the reference to the operation data. + fn data(&self) -> &Self::TrustedOperation; + /// Get hash of the operation. + fn hash(&self) -> &Self::Hash; + /// Get priority of the operation. + fn priority(&self) -> &TransactionPriority; + /// Get longevity of the operation. + fn longevity(&self) -> &TransactionLongevity; + /// Get operation dependencies. + fn requires(&self) -> &[TransactionTag]; + /// Get tags that operation provides. + fn provides(&self) -> &[TransactionTag]; + /// Return a flag indicating if the operation should be propagated to other peers. + fn is_propagable(&self) -> bool; +} + +/// TrustedOperation pool interface. +pub trait TrustedOperationPool: Send + Sync { + /// Block type. + type Block: BlockT; + /// TrustedOperation hash type. + type Hash: Hash + Eq + Member; + /// In-pool operation type. + type InPoolOperation: InPoolOperation< + TrustedOperation = StfTrustedOperation, + Hash = TxHash, + >; + /// Error type. + type Error: From + error::IntoPoolError; + + // *** RPC + + /// Returns a future that imports a bunch of unverified operations to the pool. + // FIXME: obey clippy + #[allow(clippy::type_complexity)] + fn submit_at( + &self, + at: &BlockId, + source: TrustedOperationSource, + xts: Vec, + shard: ShardIdentifier, + ) -> PoolFuture, Self::Error>>, Self::Error>; + + /// Returns a future that imports one unverified operation to the pool. + fn submit_one( + &self, + at: &BlockId, + source: TrustedOperationSource, + xt: StfTrustedOperation, + shard: ShardIdentifier, + ) -> PoolFuture, Self::Error>; + + /// Returns a future that import a single operation and starts to watch their progress in the pool. + fn submit_and_watch( + &self, + at: &BlockId, + source: TrustedOperationSource, + xt: StfTrustedOperation, + shard: ShardIdentifier, + ) -> PoolFuture, Self::Error>; + + // *** Block production / Networking + /// Get an iterator for ready operations ordered by priority. + /// + /// Guarantees to return only when operation pool got updated at `at` block. + /// Guarantees to return immediately when `None` is passed. + // FIXME: obey clippy + #[allow(clippy::type_complexity)] + fn ready_at( + &self, + at: NumberFor, + shard: ShardIdentifier, + ) -> Pin< + Box< + dyn Future> + Send>> + Send, + >, + >; + + /// Get an iterator for ready operations ordered by priority. + fn ready( + &self, + shard: ShardIdentifier, + ) -> Box> + Send>; + + /// Get an iterator over all shards. + fn shards(&self) -> Vec; + + // *** Block production + /// Remove operations identified by given hashes (and dependent operations) from the pool. + fn remove_invalid( + &self, + hashes: &[TxHash], + shard: ShardIdentifier, + inblock: bool, + ) -> Vec>; + + // *** logging + /// Returns pool status. + fn status(&self, shard: ShardIdentifier) -> PoolStatus; + + // *** logging / RPC / networking + /// Return an event stream of operations imported to the pool. + fn import_notification_stream(&self) -> ImportNotificationStream>; + + // *** networking + /// Notify the pool about operations broadcast. + fn on_broadcasted(&self, propagations: HashMap, Vec>); + + /// Returns operation hash + fn hash_of(&self, xt: &StfTrustedOperation) -> TxHash; + + /// Return specific ready operation by hash, if there is one. + fn ready_transaction( + &self, + hash: &TxHash, + shard: ShardIdentifier, + ) -> Option>; + + /// Notify the listener of top inclusion in sidechain block + fn on_block_imported(&self, hashes: &[Self::Hash], block_hash: SidechainBlockHash); +} + +/// The source of the transaction. +/// +/// Depending on the source we might apply different validation schemes. +/// For instance we can disallow specific kinds of transactions if they were not produced +/// by our local node (for instance off-chain workers). +#[derive(Copy, Clone, PartialEq, Eq, Encode, Decode, Debug)] +pub enum TrustedOperationSource { + /// Transaction is already included in block. + /// + /// This means that we can't really tell where the transaction is coming from, + /// since it's already in the received block. Note that the custom validation logic + /// using either `Local` or `External` should most likely just allow `InBlock` + /// transactions as well. + InBlock, + + /// Transaction is coming from a local source. + /// + /// This means that the transaction was produced internally by the node + /// (for instance an Off-Chain Worker, or an Off-Chain Call), as opposed + /// to being received over the network. + Local, + + /// Transaction has been received externally. + /// + /// This means the transaction has been received from (usually) "untrusted" source, + /// for instance received over the network or RPC. + External, +} + +// Replacement of primitive function from_low_u64_be +pub fn from_low_u64_to_be_h256(val: u64) -> H256 { + let mut buf = [0x0; 8]; + BigEndian::write_u64(&mut buf, val); + let capped = core::cmp::min(H256::len_bytes(), 8); + let mut bytes = [0x0; core::mem::size_of::()]; + bytes[(H256::len_bytes() - capped)..].copy_from_slice(&buf[..capped]); + H256::from_slice(&bytes) +} + +#[cfg(test)] +pub mod tests { + + use super::*; + use alloc::string::ToString; + + #[test] + pub fn test_h256() { + let tests = vec![ + ( + from_low_u64_to_be_h256(0), + "0x0000000000000000000000000000000000000000000000000000000000000000", + ), + ( + from_low_u64_to_be_h256(2), + "0x0000000000000000000000000000000000000000000000000000000000000002", + ), + ( + from_low_u64_to_be_h256(15), + "0x000000000000000000000000000000000000000000000000000000000000000f", + ), + ( + from_low_u64_to_be_h256(16), + "0x0000000000000000000000000000000000000000000000000000000000000010", + ), + ( + from_low_u64_to_be_h256(1_000), + "0x00000000000000000000000000000000000000000000000000000000000003e8", + ), + ( + from_low_u64_to_be_h256(100_000), + "0x00000000000000000000000000000000000000000000000000000000000186a0", + ), + ( + from_low_u64_to_be_h256(u64::max_value()), + "0x000000000000000000000000000000000000000000000000ffffffffffffffff", + ), + ]; + + for (number, expected) in tests { + // workaround, as H256 in no_std does not implement (de)serialize + assert_eq!(expected.to_string(), format!("{:?}", number)); + } + } +} diff --git a/tee-worker/core-primitives/top-pool/src/ready.rs b/tee-worker/core-primitives/top-pool/src/ready.rs new file mode 100644 index 0000000000..ef3ce7eeca --- /dev/null +++ b/tee-worker/core-primitives/top-pool/src/ready.rs @@ -0,0 +1,796 @@ +// This file is part of Substrate. + +// Copyright (C) 2018-2021 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +pub extern crate alloc; +use crate::{ + base_pool::TrustedOperation, + error, + future::WaitingTrustedOperations, + tracked_map::{self, ReadOnlyTrackedMap, TrackedMap}, +}; +use alloc::{boxed::Box, collections::BTreeSet, sync::Arc, vec, vec::Vec}; +use core::{cmp, cmp::Ord, default::Default, hash}; +use ita_stf::ShardIdentifier; +use log::trace; +use sp_runtime::{traits::Member, transaction_validity::TransactionTag as Tag}; +use std::collections::{HashMap, HashSet}; + +type TopErrorResult = error::Result<(Vec>>, Vec)>; + +/// An in-pool operation reference. +/// +/// Should be cheap to clone. +#[derive(Debug)] +pub struct OperationRef { + /// The actual operation data. + pub operation: Arc>, + /// Unique id when operation was inserted into the pool. + pub insertion_id: u64, +} + +impl Clone for OperationRef { + fn clone(&self) -> Self { + OperationRef { operation: self.operation.clone(), insertion_id: self.insertion_id } + } +} + +impl Ord for OperationRef { + fn cmp(&self, other: &Self) -> cmp::Ordering { + self.operation + .priority + .cmp(&other.operation.priority) + .then_with(|| other.operation.valid_till.cmp(&self.operation.valid_till)) + .then_with(|| other.insertion_id.cmp(&self.insertion_id)) + } +} + +impl PartialOrd for OperationRef { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl PartialEq for OperationRef { + fn eq(&self, other: &Self) -> bool { + self.cmp(other) == cmp::Ordering::Equal + } +} +impl Eq for OperationRef {} + +#[derive(Debug)] +pub struct ReadyTx { + /// A reference to a operation + pub operation: OperationRef, + /// A list of operations that get unlocked by this one + pub unlocks: Vec, + /// How many required tags are provided inherently + /// + /// Some operations might be already pruned from the queue, + /// so when we compute ready set we may consider this operations ready earlier. + pub requires_offset: usize, +} + +impl Clone for ReadyTx { + fn clone(&self) -> Self { + ReadyTx { + operation: self.operation.clone(), + unlocks: self.unlocks.clone(), + requires_offset: self.requires_offset, + } + } +} + +const HASH_READY: &str = r#" +Every time operation is imported its hash is placed in `ready` map and tags in `provided_tags`; +Every time operation is removed from the queue we remove the hash from `ready` map and from `provided_tags`; +Hence every hash retrieved from `provided_tags` is always present in `ready`; +qed +"#; + +#[derive(Debug)] +pub struct ReadyOperations { + /// Insertion id + insertion_id: HashMap, + /// tags that are provided by Ready operations + provided_tags: HashMap>, + /// Trusted Operations that are ready (i.e. don't have any requirements external to the pool) + ready: HashMap>>, + /// Best operations that are ready to be included to the block without any other previous operation. + best: HashMap>>, +} + +impl tracked_map::Size for ReadyTx { + fn size(&self) -> usize { + self.operation.operation.bytes + } +} + +impl Default for ReadyOperations { + fn default() -> Self { + ReadyOperations { + insertion_id: Default::default(), + provided_tags: Default::default(), + ready: Default::default(), + best: Default::default(), + } + } +} + +impl ReadyOperations { + /// Borrows a map of tags that are provided by operations in this queue. + pub fn provided_tags(&self, shard: ShardIdentifier) -> Option<&HashMap> { + if let Some(tag_pool) = &self.provided_tags.get(&shard) { + return Some(tag_pool) + } + None + } + + /// Returns an iterator of ready operations. + /// + /// Trusted Operations are returned in order: + /// 1. First by the dependencies: + /// - never return operation that requires a tag, which was not provided by one of the previously returned operations + /// 2. Then by priority: + /// - If there are two operations with all requirements satisfied the one with higher priority goes first. + /// 3. Then by the ttl that's left + /// - operations that are valid for a shorter time go first + /// 4. Lastly we sort by the time in the queue + /// - operations that are longer in the queue go first + pub fn get( + &self, + shard: ShardIdentifier, + ) -> impl Iterator>> { + // check if shard tx pool exists + if let Some(ready_map) = self.ready.get(&shard) { + return BestIterator { + all: ready_map.get_read_only_clone(), + best: self.best.get(&shard).unwrap().clone(), + awaiting: Default::default(), + } + } + let tracked_map: TrackedMap> = Default::default(); + BestIterator { + all: tracked_map.get_read_only_clone(), + best: Default::default(), + awaiting: Default::default(), + } + } + /// Returns an iterator over all shards + pub fn get_shards(&self) -> Box + '_> { + // check if shard tx pool exists + Box::new(self.ready.keys()) + } + + /// Imports operations to the pool of ready operations. + /// + /// The operation needs to have all tags satisfied (be ready) by operations + /// that are in this queue. + /// Returns operations that were replaced by the one imported. + pub fn import( + &mut self, + tx: WaitingTrustedOperations, + shard: ShardIdentifier, + ) -> error::Result>>> { + assert!( + tx.is_ready(), + "Only ready operations can be imported. Missing: {:?}", + tx.missing_tags + ); + if let Some(ready_map) = &self.ready.get(&shard) { + assert!( + !ready_map.read().contains_key(&tx.operation.hash), + "TrustedOperation is already imported." + ); + } + // Get shard pool or create if not yet existing + let current_insertion_id = self.insertion_id.entry(shard).or_insert_with(|| { + let x: u64 = Default::default(); + x + }); + + *current_insertion_id += 1; + let insertion_id = *current_insertion_id; + let hash = tx.operation.hash.clone(); + let operation = tx.operation; + + let (replaced, unlocks) = self.replace_previous(&operation, shard)?; + + let mut goes_to_best = true; + let tracked_ready = self.ready.entry(shard).or_insert_with(|| { + let x: TrackedMap> = Default::default(); + x + }); + let mut ready = tracked_ready.write(); + let mut requires_offset = 0; + // Add links to operations that unlock the current one + let tag_map = self.provided_tags.entry(shard).or_insert_with(|| { + let x: HashMap = Default::default(); + x + }); + for tag in &operation.requires { + // Check if the operation that satisfies the tag is still in the queue. + if let Some(other) = tag_map.get(tag) { + let tx = ready.get_mut(other).expect(HASH_READY); + tx.unlocks.push(hash.clone()); + // this operation depends on some other, so it doesn't go to best directly. + goes_to_best = false; + } else { + requires_offset += 1; + } + } + + // update provided_tags + // call to replace_previous guarantees that we will be overwriting + // only entries that have been removed. + + for tag in &operation.provides { + tag_map.insert(tag.clone(), hash.clone()); + } + + let operation = OperationRef { operation, insertion_id }; + + // insert to best if it doesn't require any other operation to be included before it + let best_set = self.best.entry(shard).or_insert_with(|| { + let x: BTreeSet> = Default::default(); + x + }); + if goes_to_best { + best_set.insert(operation.clone()); + } + + // insert to Ready + ready.insert(hash, ReadyTx { operation, unlocks, requires_offset }); + + Ok(replaced) + } + + /// Fold a list of ready operations to compute a single value. + pub fn fold, &ReadyTx) -> Option>( + &mut self, + f: F, + shard: ShardIdentifier, + ) -> Option { + if let Some(ready_map) = self.ready.get(&shard) { + return ready_map.read().values().fold(None, f) + } + None + } + + /// Returns true if given hash is part of the queue. + pub fn contains(&self, hash: &Hash, shard: ShardIdentifier) -> bool { + if let Some(ready_map) = self.ready.get(&shard) { + return ready_map.read().contains_key(hash) + } + false + } + + /// Retrive operation by hash + pub fn by_hash( + &self, + hash: &Hash, + shard: ShardIdentifier, + ) -> Option>> { + self.by_hashes(&[hash.clone()], shard).into_iter().next().unwrap_or(None) + } + + /// Retrieve operations by hash + pub fn by_hashes( + &self, + hashes: &[Hash], + shard: ShardIdentifier, + ) -> Vec>>> { + if let Some(ready_map) = self.ready.get(&shard) { + let ready = ready_map.read(); + return hashes + .iter() + .map(|hash| ready.get(hash).map(|x| x.operation.operation.clone())) + .collect() + } + vec![] + } + + /// Removes a subtree of operations from the ready pool. + /// + /// NOTE removing a operation will also cause a removal of all operations that depend on that one + /// (i.e. the entire subgraph that this operation is a start of will be removed). + /// All removed operations are returned. + pub fn remove_subtree( + &mut self, + hashes: &[Hash], + shard: ShardIdentifier, + ) -> Vec>> { + let to_remove = hashes.to_vec(); + self.remove_subtree_with_tag_filter(to_remove, None, shard) + } + + /// Removes a subtrees of operations trees starting from roots given in `to_remove`. + /// + /// We proceed with a particular branch only if there is at least one provided tag + /// that is not part of `provides_tag_filter`. I.e. the filter contains tags + /// that will stay in the pool, so that we can early exit and avoid descending. + fn remove_subtree_with_tag_filter( + &mut self, + mut to_remove: Vec, + provides_tag_filter: Option>, + shard: ShardIdentifier, + ) -> Vec>> { + let mut removed = vec![]; + if let Some(ready_map) = self.ready.get_mut(&shard) { + let mut ready = ready_map.write(); + while let Some(hash) = to_remove.pop() { + if let Some(mut tx) = ready.remove(&hash) { + let invalidated = tx.operation.operation.provides.iter().filter(|tag| { + provides_tag_filter + .as_ref() + .map(|filter| !filter.contains(&**tag)) + .unwrap_or(true) + }); + + let mut removed_some_tags = false; + // remove entries from provided_tags + for tag in invalidated { + removed_some_tags = true; + self.provided_tags.get_mut(&shard).unwrap().remove(tag); + } + + // remove from unlocks + for tag in &tx.operation.operation.requires { + if let Some(hash) = self.provided_tags.get(&shard).unwrap().get(tag) { + if let Some(tx) = ready.get_mut(hash) { + remove_item(&mut tx.unlocks, hash); + } + } + } + + // remove from best + self.best.get_mut(&shard).unwrap().remove(&tx.operation); + + if removed_some_tags { + // remove all operations that the current one unlocks + to_remove.append(&mut tx.unlocks); + } + + // add to removed + trace!(target: "txpool", "[{:?}] Removed as part of the subtree.", hash); + removed.push(tx.operation.operation); + } + } + } + + removed + } + + /// Removes operations that provide given tag. + /// + /// All operations that lead to a operation, which provides this tag + /// are going to be removed from the queue, but no other operations are touched - + /// i.e. all other subgraphs starting from given tag are still considered valid & ready. + pub fn prune_tags( + &mut self, + tag: Tag, + shard: ShardIdentifier, + ) -> Vec>> { + let mut removed = vec![]; + let mut to_remove = vec![tag]; + + if self.provided_tags.contains_key(&shard) { + while let Some(tag) = to_remove.pop() { + let res = self + .provided_tags + .get_mut(&shard) + .unwrap() + .remove(&tag) + .and_then(|hash| self.ready.get_mut(&shard).unwrap().write().remove(&hash)); + + if let Some(tx) = res { + let unlocks = tx.unlocks; + + // Make sure we remove it from best txs + self.best.get_mut(&shard).unwrap().remove(&tx.operation); + + let tx = tx.operation.operation; + + // prune previous operations as well + { + let hash = &tx.hash; + let mut find_previous = |tag| -> Option> { + let prev_hash = self.provided_tags.get(&shard).unwrap().get(tag)?; + let mut ready = self.ready.get_mut(&shard).unwrap().write(); + let tx2 = ready.get_mut(prev_hash)?; + remove_item(&mut tx2.unlocks, hash); + // We eagerly prune previous operations as well. + // But it might not always be good. + // Possible edge case: + // - tx provides two tags + // - the second tag enables some subgraph we don't know of yet + // - we will prune the operation + // - when we learn about the subgraph it will go to future + // - we will have to wait for re-propagation of that operation + // Alternatively the caller may attempt to re-import these operations. + if tx2.unlocks.is_empty() { + Some(tx2.operation.operation.provides.clone()) + } else { + None + } + }; + + // find previous operations + for tag in &tx.requires { + if let Some(mut tags_to_remove) = find_previous(tag) { + to_remove.append(&mut tags_to_remove); + } + } + } + + // add the operations that just got unlocked to `best` + for hash in unlocks { + if let Some(tx) = self.ready.get_mut(&shard).unwrap().write().get_mut(&hash) + { + tx.requires_offset += 1; + // this operation is ready + if tx.requires_offset == tx.operation.operation.requires.len() { + self.best.get_mut(&shard).unwrap().insert(tx.operation.clone()); + } + } + } + + // we also need to remove all other tags that this operation provides, + // but since all the hard work is done, we only clear the provided_tag -> hash + // mapping. + let current_tag = &tag; + for tag in &tx.provides { + let removed = self.provided_tags.get_mut(&shard).unwrap().remove(tag); + assert_eq!( + removed.as_ref(), + if current_tag == tag { None } else { Some(&tx.hash) }, + "The pool contains exactly one operation providing given tag; the removed operation + claims to provide that tag, so it has to be mapped to it's hash; qed" + ); + } + + removed.push(tx); + } + } + } + + removed + } + + /// Checks if the operation is providing the same tags as other operations. + /// + /// In case that's true it determines if the priority of operations that + /// we are about to replace is lower than the priority of the replacement operation. + /// We remove/replace old operations in case they have lower priority. + /// + /// In case replacement is successful returns a list of removed operations + /// and a list of hashes that are still in pool and gets unlocked by the new operation. + fn replace_previous( + &mut self, + tx: &TrustedOperation, + shard: ShardIdentifier, + ) -> TopErrorResult { + if let Some(provided_tag_map) = self.provided_tags.get(&shard) { + let (to_remove, unlocks) = { + // check if we are replacing a operation + let replace_hashes = tx + .provides + .iter() + .filter_map(|tag| provided_tag_map.get(tag)) + .collect::>(); + + // early exit if we are not replacing anything. + if replace_hashes.is_empty() { + return Ok((vec![], vec![])) + } + + // now check if collective priority is lower than the replacement operation. + let old_priority = { + let ready = self.ready.get(&shard).unwrap().read(); + replace_hashes + .iter() + .filter_map(|hash| ready.get(hash)) + .fold(0u64, |total, tx| { + total.saturating_add(tx.operation.operation.priority) + }) + }; + + // bail - the operation has too low priority to replace the old ones + if old_priority >= tx.priority { + return Err(error::Error::TooLowPriority(tx.priority)) + } + + // construct a list of unlocked operations + let unlocks = { + let ready = self.ready.get(&shard).unwrap().read(); + replace_hashes.iter().filter_map(|hash| ready.get(hash)).fold( + vec![], + |mut list, tx| { + list.extend(tx.unlocks.iter().cloned()); + list + }, + ) + }; + + (replace_hashes.into_iter().cloned().collect::>(), unlocks) + }; + + let new_provides = tx.provides.iter().cloned().collect::>(); + let removed = self.remove_subtree_with_tag_filter(to_remove, Some(new_provides), shard); + + return Ok((removed, unlocks)) + } + Ok((vec![], vec![])) + } + + /// Returns number of operations in this queue. + #[allow(clippy::len_without_is_empty)] + pub fn len(&self, shard: ShardIdentifier) -> usize { + self.ready.get(&shard).map_or(0, |ready_map| ready_map.len()) + } + + /// Returns sum of encoding lengths of all operations in this queue. + pub fn bytes(&self, shard: ShardIdentifier) -> usize { + self.ready.get(&shard).map_or(0, |ready_map| ready_map.bytes()) + } +} + +/// Iterator of ready operations ordered by priority. +pub struct BestIterator { + all: ReadOnlyTrackedMap>, + awaiting: HashMap)>, + best: BTreeSet>, +} + +/*impl Default for BestIterator { + let insertion_id = 0; + let operation = Arc::new(with_priority(3, 3)) + let tx_default = OperationRef { + insertion_id, + operation + }; + fn default() -> self.awaiting.insert("NA", (0, tx_default)) +}*/ + +impl BestIterator { + /// Depending on number of satisfied requirements insert given ref + /// either to awaiting set or to best set. + fn best_or_awaiting(&mut self, satisfied: usize, tx_ref: OperationRef) { + if satisfied >= tx_ref.operation.requires.len() { + // If we have satisfied all deps insert to best + self.best.insert(tx_ref); + } else { + // otherwise we're still awaiting for some deps + self.awaiting.insert(tx_ref.operation.hash.clone(), (satisfied, tx_ref)); + } + } +} + +impl Iterator for BestIterator { + type Item = Arc>; + + fn next(&mut self) -> Option { + loop { + let best = self.best.iter().next_back()?.clone(); + let best = self.best.take(&best)?; + + let next = self.all.read().get(&best.operation.hash).cloned(); + let ready = match next { + Some(ready) => ready, + // The operation is not in all, maybe it was removed in the meantime? + None => continue, + }; + + // Insert operations that just got unlocked. + for hash in &ready.unlocks { + // first check local awaiting operations + let res = if let Some((mut satisfied, tx_ref)) = self.awaiting.remove(hash) { + satisfied += 1; + Some((satisfied, tx_ref)) + // then get from the pool + } else { + self.all + .read() + .get(hash) + .map(|next| (next.requires_offset + 1, next.operation.clone())) + }; + + if let Some((satisfied, tx_ref)) = res { + self.best_or_awaiting(satisfied, tx_ref) + } + } + + return Some(best.operation) + } + } +} + +// See: https://github.com/rust-lang/rust/issues/40062 +fn remove_item(vec: &mut Vec, item: &T) { + if let Some(idx) = vec.iter().position(|i| i == item) { + vec.swap_remove(idx); + } +} + +#[cfg(test)] +pub mod tests { + use super::*; + use crate::primitives::TrustedOperationSource as Source; + + fn tx(id: u8) -> TrustedOperation> { + TrustedOperation { + data: vec![id], + bytes: 1, + hash: id as u64, + priority: 1, + valid_till: 2, + requires: vec![vec![1], vec![2]], + provides: vec![vec![3], vec![4]], + propagate: true, + source: Source::External, + } + } + + fn import( + ready: &mut ReadyOperations, + tx: TrustedOperation, + shard: ShardIdentifier, + ) -> error::Result>>> { + let x = WaitingTrustedOperations::new(tx, ready.provided_tags(shard), &[]); + ready.import(x, shard) + } + + #[test] + pub fn test_should_replace_transaction_that_provides_the_same_tag() { + // given + let shard = ShardIdentifier::default(); + let mut ready = ReadyOperations::default(); + let mut tx1 = tx(1); + tx1.requires.clear(); + let mut tx2 = tx(2); + tx2.requires.clear(); + tx2.provides = vec![vec![3]]; + let mut tx3 = tx(3); + tx3.requires.clear(); + tx3.provides = vec![vec![4]]; + + // when + import(&mut ready, tx2, shard).unwrap(); + import(&mut ready, tx3, shard).unwrap(); + assert_eq!(ready.get(shard).count(), 2); + + // too low priority + import(&mut ready, tx1.clone(), shard).unwrap_err(); + + tx1.priority = 10; + import(&mut ready, tx1, shard).unwrap(); + + // then + assert_eq!(ready.get(shard).count(), 1); + } + + #[test] + pub fn test_should_replace_multiple_transactions_correctly() { + // given + let shard = ShardIdentifier::default(); + let mut ready = ReadyOperations::default(); + let mut tx0 = tx(0); + tx0.requires = vec![]; + tx0.provides = vec![vec![0]]; + let mut tx1 = tx(1); + tx1.requires = vec![]; + tx1.provides = vec![vec![1]]; + let mut tx2 = tx(2); + tx2.requires = vec![vec![0], vec![1]]; + tx2.provides = vec![vec![2], vec![3]]; + let mut tx3 = tx(3); + tx3.requires = vec![vec![2]]; + tx3.provides = vec![vec![4]]; + let mut tx4 = tx(4); + tx4.requires = vec![vec![3]]; + tx4.provides = vec![vec![5]]; + // replacement + let mut tx2_2 = tx(5); + tx2_2.requires = vec![vec![0], vec![1]]; + tx2_2.provides = vec![vec![2]]; + tx2_2.priority = 10; + + for tx in vec![tx0, tx1, tx2, tx3, tx4] { + import(&mut ready, tx, shard).unwrap(); + } + assert_eq!(ready.get(shard).count(), 5); + + // when + import(&mut ready, tx2_2, shard).unwrap(); + + // then + assert_eq!(ready.get(shard).count(), 3); + } + + #[test] + pub fn test_should_return_best_transactions_in_correct_order() { + // given + let shard = ShardIdentifier::default(); + let mut ready = ReadyOperations::default(); + let mut tx1 = tx(1); + tx1.requires.clear(); + let mut tx2 = tx(2); + tx2.requires = tx1.provides.clone(); + tx2.provides = vec![vec![106]]; + let mut tx3 = tx(3); + tx3.requires = vec![tx1.provides[0].clone(), vec![106]]; + tx3.provides = vec![]; + let mut tx4 = tx(4); + tx4.requires = vec![tx1.provides[0].clone()]; + tx4.provides = vec![]; + let tx5 = TrustedOperation { + data: vec![5], + bytes: 1, + hash: 5, + priority: 1, + valid_till: u64::max_value(), // use the max_value() here for testing. + requires: vec![tx1.provides[0].clone()], + provides: vec![], + propagate: true, + source: Source::External, + }; + + // when + for tx in vec![tx1, tx2, tx3, tx4, tx5] { + import(&mut ready, tx, shard).unwrap(); + } + + // then + assert_eq!(ready.best.len(), 1); + + let mut it = ready.get(shard).map(|tx| tx.data[0]); + + assert_eq!(it.next(), Some(1)); + assert_eq!(it.next(), Some(2)); + assert_eq!(it.next(), Some(3)); + assert_eq!(it.next(), Some(4)); + assert_eq!(it.next(), Some(5)); + assert_eq!(it.next(), None); + } + + #[test] + pub fn test_should_order_refs() { + let mut id = 1; + let mut with_priority = |priority, longevity| { + id += 1; + let mut tx = tx(id); + tx.priority = priority; + tx.valid_till = longevity; + tx + }; + // higher priority = better + assert!( + OperationRef { operation: Arc::new(with_priority(3, 3)), insertion_id: 1 } + > OperationRef { operation: Arc::new(with_priority(2, 3)), insertion_id: 2 } + ); + // lower validity = better + assert!( + OperationRef { operation: Arc::new(with_priority(3, 2)), insertion_id: 1 } + > OperationRef { operation: Arc::new(with_priority(3, 3)), insertion_id: 2 } + ); + // lower insertion_id = better + assert!( + OperationRef { operation: Arc::new(with_priority(3, 3)), insertion_id: 1 } + > OperationRef { operation: Arc::new(with_priority(3, 3)), insertion_id: 2 } + ); + } +} diff --git a/tee-worker/core-primitives/top-pool/src/rotator.rs b/tee-worker/core-primitives/top-pool/src/rotator.rs new file mode 100644 index 0000000000..0a08173714 --- /dev/null +++ b/tee-worker/core-primitives/top-pool/src/rotator.rs @@ -0,0 +1,216 @@ +// This file is part of Substrate. + +// Copyright (C) 2018-2020 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +//! Rotate extrinsic inside the pool. +//! +//! Keeps only recent extrinsic and discard the ones kept for a significant amount of time. +//! Discarded extrinsics are banned so that they don't get re-imported again. + +#[cfg(all(not(feature = "std"), feature = "sgx"))] +use std::sync::SgxRwLock as RwLock; + +#[cfg(feature = "std")] +use std::sync::RwLock; + +use crate::base_pool::TrustedOperation; +use std::{ + collections::HashMap, + hash, iter, + time::{Duration, Instant}, +}; + +/// Expected size of the banned extrinsics cache. +const EXPECTED_SIZE: usize = 2048; + +/// Pool rotator is responsible to only keep fresh extrinsics in the pool. +/// +/// Extrinsics that occupy the pool for too long are culled and temporarily banned from entering +/// the pool again. +pub struct PoolRotator { + /// How long the extrinsic is banned for. + ban_time: Duration, + /// Currently banned extrinsics. + banned_until: RwLock>, +} + +impl Default for PoolRotator { + fn default() -> Self { + PoolRotator { ban_time: Duration::from_secs(60 * 30), banned_until: Default::default() } + } +} + +impl PoolRotator { + /// Returns `true` if extrinsic hash is currently banned. + pub fn is_banned(&self, hash: &Hash) -> bool { + self.banned_until.read().unwrap().contains_key(hash) + } + + /// Bans given set of hashes. + pub fn ban(&self, now: &Instant, hashes: impl IntoIterator) { + let mut banned = self.banned_until.write().unwrap(); + + for hash in hashes { + banned.insert(hash.clone(), *now + self.ban_time); + } + + if banned.len() > 2 * EXPECTED_SIZE { + while banned.len() > EXPECTED_SIZE { + if let Some(key) = banned.keys().next().cloned() { + banned.remove(&key); + } + } + } + } + + /// Bans extrinsic if it's stale. + /// + /// Returns `true` if extrinsic is stale and got banned. + pub fn ban_if_stale( + &self, + now: &Instant, + current_block: u64, + xt: &TrustedOperation, + ) -> bool { + if xt.valid_till > current_block { + return false + } + + self.ban(now, iter::once(xt.hash.clone())); + true + } + + /// Removes timed bans. + pub fn clear_timeouts(&self, now: &Instant) { + let mut banned = self.banned_until.write().unwrap(); + + banned.retain(|_, &mut v| v >= *now); + } +} + +#[cfg(test)] +pub mod tests { + use super::*; + use crate::primitives::TrustedOperationSource; + + type Hash = u64; + type Ex = (); + + fn rotator() -> PoolRotator { + PoolRotator { ban_time: Duration::from_millis(1000), ..Default::default() } + } + + fn tx() -> (Hash, TrustedOperation) { + let hash = 5u64; + let tx = TrustedOperation { + data: (), + bytes: 1, + hash, + priority: 5, + valid_till: 1, + requires: vec![], + provides: vec![], + propagate: true, + source: TrustedOperationSource::External, + }; + + (hash, tx) + } + + #[test] + pub fn test_should_not_ban_if_not_stale() { + // given + let (hash, tx) = tx(); + let rotator = rotator(); + assert!(!rotator.is_banned(&hash)); + let now = Instant::now(); + let past_block = 0; + + // when + assert!(!rotator.ban_if_stale(&now, past_block, &tx)); + + // then + assert!(!rotator.is_banned(&hash)); + } + + #[test] + pub fn test_should_ban_stale_extrinsic() { + // given + let (hash, tx) = tx(); + let rotator = rotator(); + assert!(!rotator.is_banned(&hash)); + + // when + assert!(rotator.ban_if_stale(&Instant::now(), 1, &tx)); + + // then + assert!(rotator.is_banned(&hash)); + } + + #[test] + pub fn test_should_clear_banned() { + // given + let (hash, tx) = tx(); + let rotator = rotator(); + assert!(rotator.ban_if_stale(&Instant::now(), 1, &tx)); + assert!(rotator.is_banned(&hash)); + + // when + let future = Instant::now() + rotator.ban_time + rotator.ban_time; + rotator.clear_timeouts(&future); + + // then + assert!(!rotator.is_banned(&hash)); + } + + #[test] + pub fn test_should_garbage_collect() { + // given + fn tx_with(i: u64, valid_till: u64) -> TrustedOperation { + let hash = i; + TrustedOperation { + data: (), + bytes: 2, + hash, + priority: 5, + valid_till, + requires: vec![], + provides: vec![], + propagate: true, + source: TrustedOperationSource::External, + } + } + + let rotator = rotator(); + + let now = Instant::now(); + let past_block = 0; + + // when + for i in 0..2 * EXPECTED_SIZE { + let tx = tx_with(i as u64, past_block); + assert!(rotator.ban_if_stale(&now, past_block, &tx)); + } + assert_eq!(rotator.banned_until.read().unwrap().len(), 2 * EXPECTED_SIZE); + + // then + let tx = tx_with(2 * EXPECTED_SIZE as u64, past_block); + // trigger a garbage collection + assert!(rotator.ban_if_stale(&now, past_block, &tx)); + assert_eq!(rotator.banned_until.read().unwrap().len(), EXPECTED_SIZE); + } +} diff --git a/tee-worker/core-primitives/top-pool/src/tracked_map.rs b/tee-worker/core-primitives/top-pool/src/tracked_map.rs new file mode 100644 index 0000000000..dacbe841dd --- /dev/null +++ b/tee-worker/core-primitives/top-pool/src/tracked_map.rs @@ -0,0 +1,198 @@ +// This file is part of Substrate. + +// Copyright (C) 2018-2020 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +pub extern crate alloc; +use alloc::sync::Arc; +use core::{ + clone::Clone, + cmp, hash, + sync::atomic::{AtomicIsize, Ordering as AtomicOrdering}, +}; +use std::collections::{hash_map::Values, HashMap}; + +//use parking_lot::{RwLock, RwLockWriteGuard, RwLockReadGuard}; + +/// Something that can report it's size. +pub trait Size { + fn size(&self) -> usize; +} + +/// Map with size tracking. +/// +/// Size reported might be slightly off and only approximately true. +#[derive(Debug)] +pub struct TrackedMap { + index: Arc>, + bytes: AtomicIsize, + length: AtomicIsize, +} + +impl Default for TrackedMap { + fn default() -> Self { + Self { index: Arc::new(HashMap::new()), bytes: 0.into(), length: 0.into() } + } +} + +impl TrackedMap { + /// Current tracked length of the content. + pub fn len(&self) -> usize { + cmp::max(self.length.load(AtomicOrdering::Relaxed), 0) as usize + } + + /// Returns true if Map is empty + pub fn is_empty(&self) -> bool { + self.length.load(AtomicOrdering::Relaxed) == 0 + } + + /// Current sum of content length. + pub fn bytes(&self) -> usize { + cmp::max(self.bytes.load(AtomicOrdering::Relaxed), 0) as usize + } + + /// Read-only clone of the interior. + pub fn get_read_only_clone(&self) -> ReadOnlyTrackedMap { + ReadOnlyTrackedMap(self.index.clone()) + } + + /// Read Access - no data race safety + pub fn read(&self) -> TrackedMapReadAccess { + TrackedMapReadAccess { inner_guard: self.index.clone() } + } + + /// Write Access - no data race safety + pub fn write(&mut self) -> TrackedMapWriteAccess { + TrackedMapWriteAccess { + //inner_guard: self.index.make_mut(&self), + inner_guard: Arc::make_mut(&mut self.index), + bytes: &self.bytes, + length: &self.length, + } + } +} + +/// Read-only access to map. +/// +/// The only thing can be done is .read(). +pub struct ReadOnlyTrackedMap(Arc>); + +impl ReadOnlyTrackedMap +where + K: Eq + hash::Hash, +{ + /// Lock map for read. + pub fn read(&self) -> TrackedMapReadAccess { + TrackedMapReadAccess { inner_guard: self.0.clone() } + } +} + +pub struct TrackedMapReadAccess { + inner_guard: Arc>, +} + +impl TrackedMapReadAccess +where + K: Eq + hash::Hash, +{ + /// Returns true if map contains key. + pub fn contains_key(&self, key: &K) -> bool { + self.inner_guard.contains_key(key) + } + + /// Returns reference to the contained value by key, if exists. + pub fn get(&self, key: &K) -> Option<&V> { + self.inner_guard.get(key) + } + + /// Returns iterator over all values. + pub fn values(&self) -> Values { + self.inner_guard.values() + } +} + +pub struct TrackedMapWriteAccess<'a, K, V> { + bytes: &'a AtomicIsize, + length: &'a AtomicIsize, + inner_guard: &'a mut HashMap, +} + +impl<'a, K, V> TrackedMapWriteAccess<'a, K, V> +where + K: Eq + hash::Hash, + V: Size, +{ + /// Insert value and return previous (if any). + pub fn insert(&mut self, key: K, val: V) -> Option { + let new_bytes = val.size(); + self.bytes.fetch_add(new_bytes as isize, AtomicOrdering::Relaxed); + self.length.fetch_add(1, AtomicOrdering::Relaxed); + self.inner_guard.insert(key, val).map(|old_val| { + self.bytes.fetch_sub(old_val.size() as isize, AtomicOrdering::Relaxed); + self.length.fetch_sub(1, AtomicOrdering::Relaxed); + old_val + }) + } + + /// Remove value by key. + pub fn remove(&mut self, key: &K) -> Option { + let val = self.inner_guard.remove(key); + if let Some(size) = val.as_ref().map(Size::size) { + self.bytes.fetch_sub(size as isize, AtomicOrdering::Relaxed); + self.length.fetch_sub(1, AtomicOrdering::Relaxed); + } + val + } + + /// Returns mutable reference to the contained value by key, if exists. + pub fn get_mut(&mut self, key: &K) -> Option<&mut V> { + self.inner_guard.get_mut(key) + } +} + +#[cfg(test)] +pub mod tests { + + use super::*; + + impl Size for i32 { + fn size(&self) -> usize { + *self as usize / 10 + } + } + + #[test] + pub fn test_basic() { + let mut map = TrackedMap::default(); + + assert!(map.is_empty()); + + map.write().insert(5, 10); + map.write().insert(6, 20); + + assert_eq!(map.bytes(), 3); + assert_eq!(map.len(), 2); + + map.write().insert(6, 30); + + assert_eq!(map.bytes(), 4); + assert_eq!(map.len(), 2); + + map.write().remove(&6); + assert_eq!(map.bytes(), 1); + assert_eq!(map.len(), 1); + } +} diff --git a/tee-worker/core-primitives/top-pool/src/validated_pool.rs b/tee-worker/core-primitives/top-pool/src/validated_pool.rs new file mode 100644 index 0000000000..7c7eccb7a4 --- /dev/null +++ b/tee-worker/core-primitives/top-pool/src/validated_pool.rs @@ -0,0 +1,716 @@ +// This file is part of Substrate. + +// Copyright (C) 2018-2020 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#[cfg(all(not(feature = "std"), feature = "sgx"))] +use crate::sgx_reexport_prelude::*; + +#[cfg(all(not(feature = "std"), feature = "sgx"))] +use std::sync::SgxMutex as Mutex; +#[cfg(all(not(feature = "std"), feature = "sgx"))] +use std::sync::SgxRwLock as RwLock; + +#[cfg(feature = "std")] +use std::sync::Mutex; +#[cfg(feature = "std")] +use std::sync::RwLock; + +use crate::{ + base_pool as base, + base_pool::PruneStatus, + error, + listener::Listener, + pool::{ChainApi, EventStream, ExtrinsicHash, Options, TransactionFor}, + primitives::{PoolStatus, TrustedOperationSource}, + rotator::PoolRotator, +}; +use codec::Encode; +use core::{hash, result::Result}; +use ita_stf::{ShardIdentifier, TrustedOperation as StfTrustedOperation}; +use itc_direct_rpc_server::SendRpcResponse; +use itp_types::BlockHash as SidechainBlockHash; +use jsonrpc_core::futures::channel::mpsc::{channel, Sender}; +use sp_runtime::{ + generic::BlockId, + traits::{self, SaturatedConversion}, + transaction_validity::{TransactionTag as Tag, ValidTransaction}, +}; +use std::{ + collections::{HashMap, HashSet}, + format, + string::String, + sync::Arc, + time::Instant, + vec, + vec::Vec, +}; + +/// Pre-validated operation. Validated pool only accepts operations wrapped in this enum. +#[derive(Debug)] +pub enum ValidatedOperation { + /// TrustedOperation that has been validated successfully. + Valid(base::TrustedOperation), + /// TrustedOperation that is invalid. + Invalid(Hash, Error), + /// TrustedOperation which validity can't be determined. + /// + /// We're notifying watchers about failure, if 'unknown' operation is submitted. + Unknown(Hash, Error), +} + +impl ValidatedOperation { + /// Consume validity result, operation data and produce ValidTransaction. + pub fn valid_at( + at: u64, + hash: Hash, + source: TrustedOperationSource, + data: Ex, + bytes: usize, + validity: ValidTransaction, + ) -> Self { + Self::Valid(base::TrustedOperation { + data, + bytes, + hash, + source, + priority: validity.priority, + requires: validity.requires, + provides: validity.provides, + propagate: validity.propagate, + valid_till: at.saturated_into::().saturating_add(validity.longevity), + }) + } +} + +/// A type of validated operation stored in the pool. +pub type ValidatedOperationFor = + ValidatedOperation, StfTrustedOperation, ::Error>; + +/// Pool that deals with validated operations. +pub struct ValidatedPool +where + R: SendRpcResponse>, +{ + api: Arc, + options: Options, + listener: RwLock, R>>, + pool: RwLock, StfTrustedOperation>>, + import_notification_sinks: Mutex>>>, + rotator: PoolRotator>, +} + +impl ValidatedPool +where + R: SendRpcResponse>, +{ + /// Create a new operation pool. + pub fn new(options: Options, api: Arc, rpc_response_sender: Arc) -> Self { + let base_pool = base::BasePool::new(options.reject_future_operations); + ValidatedPool { + options, + listener: RwLock::new(Listener::new(rpc_response_sender)), + api, + pool: RwLock::new(base_pool), + import_notification_sinks: Default::default(), + rotator: Default::default(), + } + } + + /// Bans given set of hashes. + pub fn ban(&self, now: &Instant, hashes: impl IntoIterator>) { + self.rotator.ban(now, hashes) + } + + /// Returns true if operation with given hash is currently banned from the pool. + pub fn is_banned(&self, hash: &ExtrinsicHash) -> bool { + self.rotator.is_banned(hash) + } + + /// A fast check before doing any further processing of a operation, like validation. + /// + /// If `ingore_banned` is `true`, it will not check if the operation is banned. + /// + /// It checks if the operation is already imported or banned. If so, it returns an error. + pub fn check_is_known( + &self, + tx_hash: &ExtrinsicHash, + ignore_banned: bool, + shard: ShardIdentifier, + ) -> Result<(), B::Error> { + if !ignore_banned && self.is_banned(tx_hash) { + Err(error::Error::TemporarilyBanned.into()) + } else if self.pool.read().unwrap().is_imported(tx_hash, shard) { + Err(error::Error::AlreadyImported.into()) + } else { + Ok(()) + } + } + + /// Imports a bunch of pre-validated operations to the pool. + pub fn submit( + &self, + txs: impl IntoIterator>, + shard: ShardIdentifier, + ) -> Vec, B::Error>> { + let results = txs + .into_iter() + .map(|validated_tx| self.submit_one(validated_tx, shard)) + .collect::>(); + + // only enforce limits if there is at least one imported operation + let removed = if results.iter().any(|res| res.is_ok()) { + self.enforce_limits(shard) + } else { + Default::default() + }; + + results + .into_iter() + .map(|res| match res { + Ok(ref hash) if removed.contains(hash) => + Err(error::Error::ImmediatelyDropped.into()), + other => other, + }) + .collect() + } + + /// Submit single pre-validated operation to the pool. + fn submit_one( + &self, + tx: ValidatedOperationFor, + shard: ShardIdentifier, + ) -> Result, B::Error> { + match tx { + ValidatedOperation::Valid(tx) => { + let imported = + self.pool.write().map_err(|_| error::Error::UnlockError)?.import(tx, shard)?; + + if let base::Imported::Ready { ref hash, .. } = imported { + self.import_notification_sinks + .lock() + .map_err(|_| error::Error::UnlockError)? + .retain_mut(|sink| match sink.try_send(*hash) { + Ok(()) => true, + Err(e) => + if e.is_full() { + log::warn!(target: "txpool", "[{:?}] Trying to notify an import but the channel is full", hash); + true + } else { + false + }, + }); + } + + let mut listener = self.listener.write().map_err(|_| error::Error::UnlockError)?; + fire_events(&mut listener, &imported); + Ok(*imported.hash()) + }, + ValidatedOperation::Invalid(hash, err) => { + self.rotator.ban(&Instant::now(), core::iter::once(hash)); + Err(err) + }, + ValidatedOperation::Unknown(hash, err) => { + self.listener.write().unwrap().invalid(&hash); + Err(err) + }, + } + } + + fn enforce_limits(&self, shard: ShardIdentifier) -> HashSet> { + let status = self.pool.read().unwrap().status(shard); + let ready_limit = &self.options.ready; + let future_limit = &self.options.future; + + log::debug!(target: "txpool", "Pool Status: {:?}", status); + if ready_limit.is_exceeded(status.ready, status.ready_bytes) + || future_limit.is_exceeded(status.future, status.future_bytes) + { + log::debug!( + target: "txpool", + "Enforcing limits ({}/{}kB ready, {}/{}kB future", + ready_limit.count, ready_limit.total_bytes / 1024, + future_limit.count, future_limit.total_bytes / 1024, + ); + + // clean up the pool + let removed = { + let mut pool = self.pool.write().unwrap(); + let removed = pool + .enforce_limits(ready_limit, future_limit, shard) + .into_iter() + .map(|x| x.hash) + .collect::>(); + // ban all removed operations + self.rotator.ban(&Instant::now(), removed.iter().copied()); + removed + }; + if !removed.is_empty() { + log::debug!(target: "txpool", "Enforcing limits: {} dropped", removed.len()); + } + + // run notifications + let mut listener = self.listener.write().unwrap(); + for h in &removed { + listener.dropped(h, None); + } + + removed + } else { + Default::default() + } + } + + /// Import a single extrinsic and starts to watch their progress in the pool. + pub fn submit_and_watch( + &self, + tx: ValidatedOperationFor, + shard: ShardIdentifier, + ) -> Result, B::Error> { + match tx { + ValidatedOperation::Valid(tx) => { + let hash_result = self + .submit(core::iter::once(ValidatedOperation::Valid(tx)), shard) + .pop() + .expect("One extrinsic passed; one result returned; qed"); + // TODO: How to return / notice if Future or Ready queue? + if let Ok(hash) = hash_result { + self.listener.write().unwrap().create_watcher(hash); + } + hash_result + }, + ValidatedOperation::Invalid(hash, err) => { + self.rotator.ban(&Instant::now(), core::iter::once(hash)); + Err(err) + }, + ValidatedOperation::Unknown(_, err) => Err(err), + } + } + + /// Resubmits revalidated operations back to the pool. + /// + /// Removes and then submits passed operations and all dependent operations. + /// Transactions that are missing from the pool are not submitted. + pub fn resubmit( + &self, + mut updated_transactions: HashMap, ValidatedOperationFor>, + shard: ShardIdentifier, + ) { + #[derive(Debug, Clone, Copy, PartialEq)] + enum Status { + Future, + Ready, + Failed, + Dropped, + } + + let (mut initial_statuses, final_statuses) = { + let mut pool = self.pool.write().unwrap(); + + // remove all passed operations from the ready/future queues + // (this may remove additional operations as well) + // + // for every operation that has an entry in the `updated_transactions`, + // we store updated validation result in txs_to_resubmit + // for every operation that has no entry in the `updated_transactions`, + // we store last validation result (i.e. the pool entry) in txs_to_resubmit + let mut initial_statuses = HashMap::new(); + let mut txs_to_resubmit = Vec::with_capacity(updated_transactions.len()); + while !updated_transactions.is_empty() { + let hash = updated_transactions + .keys() + .next() + .cloned() + .expect("operations is not empty; qed"); + + // note we are not considering tx with hash invalid here - we just want + // to remove it along with dependent operations and `remove_subtree()` + // does exactly what we need + let removed = pool.remove_subtree(&[hash], shard); + for removed_tx in removed { + let removed_hash = removed_tx.hash; + let updated_transaction = updated_transactions.remove(&removed_hash); + let tx_to_resubmit = if let Some(updated_tx) = updated_transaction { + updated_tx + } else { + // in most cases we'll end up in successful `try_unwrap`, but if not + // we still need to reinsert operation back to the pool => duplicate call + let operation = match Arc::try_unwrap(removed_tx) { + Ok(operation) => operation, + Err(operation) => operation.duplicate(), + }; + ValidatedOperation::Valid(operation) + }; + + initial_statuses.insert(removed_hash, Status::Ready); + txs_to_resubmit.push((removed_hash, tx_to_resubmit)); + } + // make sure to remove the hash even if it's not present in the pool any more. + updated_transactions.remove(&hash); + } + + // if we're rejecting future operations, then insertion order matters here: + // if tx1 depends on tx2, then if tx1 is inserted before tx2, then it goes + // to the future queue and gets rejected immediately + // => let's temporary stop rejection and clear future queue before return + pool.with_futures_enabled(|pool, reject_future_operations| { + // now resubmit all removed operations back to the pool + let mut final_statuses = HashMap::new(); + for (hash, tx_to_resubmit) in txs_to_resubmit { + match tx_to_resubmit { + ValidatedOperation::Valid(tx) => match pool.import(tx, shard) { + Ok(imported) => match imported { + base::Imported::Ready { promoted, failed, removed, .. } => { + final_statuses.insert(hash, Status::Ready); + for hash in promoted { + final_statuses.insert(hash, Status::Ready); + } + for hash in failed { + final_statuses.insert(hash, Status::Failed); + } + for tx in removed { + final_statuses.insert(tx.hash, Status::Dropped); + } + }, + base::Imported::Future { .. } => { + final_statuses.insert(hash, Status::Future); + }, + }, + Err(err) => { + // we do not want to fail if single operation import has failed + // nor we do want to propagate this error, because it could tx unknown to caller + // => let's just notify listeners (and issue debug message) + log::warn!( + target: "txpool", + "[{:?}] Removing invalid operation from update: {:?}", + hash, + err, + ); + final_statuses.insert(hash, Status::Failed); + }, + }, + ValidatedOperation::Invalid(_, _) | ValidatedOperation::Unknown(_, _) => { + final_statuses.insert(hash, Status::Failed); + }, + } + } + + // if the pool is configured to reject future operations, let's clear the future + // queue, updating final statuses as required + if reject_future_operations { + for future_tx in pool.clear_future(shard) { + final_statuses.insert(future_tx.hash, Status::Dropped); + } + } + + (initial_statuses, final_statuses) + }) + }; + + // and now let's notify listeners about status changes + let mut listener = self.listener.write().unwrap(); + for (hash, final_status) in final_statuses { + let initial_status = initial_statuses.remove(&hash); + if initial_status.is_none() || Some(final_status) != initial_status { + match final_status { + Status::Future => listener.future(&hash), + Status::Ready => listener.ready(&hash, None), + Status::Dropped => listener.dropped(&hash, None), + Status::Failed => listener.invalid(&hash), + } + } + } + } + + /// For each extrinsic, returns tags that it provides (if known), or None (if it is unknown). + pub fn extrinsics_tags( + &self, + hashes: &[ExtrinsicHash], + shard: ShardIdentifier, + ) -> Vec>> { + self.pool + .read() + .unwrap() + .by_hashes(hashes, shard) + .into_iter() + .map(|existing_in_pool| existing_in_pool.map(|operation| operation.provides.to_vec())) + .collect() + } + + /// Get ready operation by hash + pub fn ready_by_hash( + &self, + hash: &ExtrinsicHash, + shard: ShardIdentifier, + ) -> Option> { + self.pool.read().unwrap().ready_by_hash(hash, shard) + } + + /// Prunes ready operations that provide given list of tags. + pub fn prune_tags( + &self, + tags: impl IntoIterator, + shard: ShardIdentifier, + ) -> Result, StfTrustedOperation>, B::Error> { + // Perform tag-based pruning in the base pool + let status = self.pool.write().unwrap().prune_tags(tags, shard); + // Notify event listeners of all operations + // that were promoted to `Ready` or were dropped. + { + let mut listener = self.listener.write().unwrap(); + for promoted in &status.promoted { + fire_events(&mut *listener, promoted); + } + for f in &status.failed { + listener.dropped(f, None); + } + } + + Ok(status) + } + + /// Resubmit operations that have been revalidated after prune_tags call. + pub fn resubmit_pruned( + &self, + at: &BlockId, + known_imported_hashes: impl IntoIterator> + Clone, + pruned_hashes: Vec>, + pruned_xts: Vec>, + shard: ShardIdentifier, + ) -> Result<(), B::Error> + where + ::Error: error::IntoPoolError, + { + debug_assert_eq!(pruned_hashes.len(), pruned_xts.len()); + + // Resubmit pruned operations + let results = self.submit(pruned_xts, shard); + + // Collect the hashes of operations that now became invalid (meaning that they are successfully pruned). + let hashes = results.into_iter().enumerate().filter_map(|(idx, r)| { + match r.map_err(error::IntoPoolError::into_pool_error) { + Err(Ok(error::Error::InvalidTrustedOperation)) => Some(pruned_hashes[idx]), + _ => None, + } + }); + // Fire `pruned` notifications for collected hashes and make sure to include + // `known_imported_hashes` since they were just imported as part of the block. + let hashes = hashes.chain(known_imported_hashes.into_iter()); + self.fire_pruned(at, hashes)?; + + // perform regular cleanup of old operations in the pool + // and update temporary bans. + self.clear_stale(at, shard)?; + Ok(()) + } + + /// Fire notifications for pruned operations. + pub fn fire_pruned( + &self, + at: &BlockId, + hashes: impl Iterator>, + ) -> Result<(), B::Error> { + let header_hash = self + .api + .block_id_to_hash(at)? + .ok_or_else(|| error::Error::InvalidBlockId(format!("{:?}", at)))?; + let mut listener = self.listener.write().unwrap(); + let mut set = HashSet::with_capacity(hashes.size_hint().0); + for h in hashes { + // `hashes` has possibly duplicate hashes. + // we'd like to send out the `InBlock` notification only once. + if !set.contains(&h) { + listener.pruned(header_hash, &h); + set.insert(h); + } + } + Ok(()) + } + + /// Removes stale operations from the pool. + /// + /// Stale operations are operation beyond their longevity period. + /// Note this function does not remove operations that are already included in the chain. + /// See `prune_tags` if you want this. + pub fn clear_stale( + &self, + at: &BlockId, + shard: ShardIdentifier, + ) -> Result<(), B::Error> { + let block_number = self + .api + .block_id_to_number(at)? + .ok_or_else(|| error::Error::InvalidBlockId(format!("{:?}", at)))? + .saturated_into::(); + let now = Instant::now(); + let to_remove = { + self.ready(shard) + .filter(|tx| self.rotator.ban_if_stale(&now, block_number, tx)) + .map(|tx| tx.hash) + .collect::>() + }; + let futures_to_remove: Vec> = { + let p = self.pool.read().unwrap(); + let mut hashes = Vec::new(); + for tx in p.futures(shard) { + if self.rotator.ban_if_stale(&now, block_number, tx) { + hashes.push(tx.hash); + } + } + hashes + }; + // removing old operations + self.remove_invalid(&to_remove, shard, false); + self.remove_invalid(&futures_to_remove, shard, false); + // clear banned operations timeouts + self.rotator.clear_timeouts(&now); + + Ok(()) + } + + /// Get rotator reference. + /// only used for test + pub fn rotator(&self) -> &PoolRotator> { + &self.rotator + } + + /// Get api reference. + pub fn api(&self) -> &B { + &self.api + } + + /// Return an event stream of notifications for when operations are imported to the pool. + /// + /// Consumers of this stream should use the `ready` method to actually get the + /// pending operations in the right order. + pub fn import_notification_stream(&self) -> EventStream> { + const CHANNEL_BUFFER_SIZE: usize = 1024; + + let (sink, stream) = channel(CHANNEL_BUFFER_SIZE); + self.import_notification_sinks.lock().unwrap().push(sink); + stream + } + + /// Invoked when extrinsics are broadcasted. + pub fn on_broadcasted(&self, propagated: HashMap, Vec>) { + let mut listener = self.listener.write().unwrap(); + for (hash, peers) in propagated.into_iter() { + listener.broadcasted(&hash, peers); + } + } + + /// Remove a subtree of operations from the pool and mark them invalid. + /// + /// The operations passed as an argument will be additionally banned + /// to prevent them from entering the pool right away. + /// Note this is not the case for the dependent operations - those may + /// still be valid so we want to be able to re-import them. + pub fn remove_invalid( + &self, + hashes: &[ExtrinsicHash], + shard: ShardIdentifier, + inblock: bool, + ) -> Vec> { + // early exit in case there is no invalid operations. + if hashes.is_empty() { + return vec![] + } + + let invalid = self.pool.write().unwrap().remove_subtree(hashes, shard); + + log::debug!(target: "txpool", "Removed invalid operations: {:?}", invalid); + + let mut listener = self.listener.write().unwrap(); + if inblock { + for _tx in &invalid { + //listener.in_block(&tx.hash); + } + } else { + // temporarily ban invalid operations + self.rotator.ban(&Instant::now(), hashes.iter().cloned()); + for tx in &invalid { + listener.invalid(&tx.hash); + } + } + + invalid + } + + /// Get an iterator for ready operations ordered by priority + pub fn ready(&self, shard: ShardIdentifier) -> impl Iterator> + Send { + self.pool.read().unwrap().ready(shard) + } + + /// Get an iterator for all shards + pub fn shards(&self) -> Vec { + let mut shards = vec![]; + let base_pool = self.pool.read().unwrap(); + let shard_iterator = base_pool.get_shards(); + for shard in shard_iterator { + shards.push(*shard); + } + shards + } + + /// Returns pool status. + pub fn status(&self, shard: ShardIdentifier) -> PoolStatus { + self.pool.read().unwrap().status(shard) + } + + /// Notify all watchers that operations in the block with hash have been finalized + pub async fn on_block_finalized(&self, block_hash: SidechainBlockHash) -> Result<(), B::Error> + where + <::Block as sp_runtime::traits::Block>::Hash: core::fmt::Display, + { + log::trace!(target: "txpool", "Attempting to notify watchers of finalization for {}", block_hash); + self.listener.write().unwrap().finalized(block_hash); + Ok(()) + } + + /// Notify the listener of retracted blocks + pub fn on_block_retracted(&self, block_hash: SidechainBlockHash) { + self.listener.write().unwrap().retracted(block_hash) + } + + /// Notify the listener of top inclusion in sidechain block + pub fn on_block_imported(&self, hashes: &[ExtrinsicHash], block_hash: SidechainBlockHash) { + for top_hash in hashes.iter() { + self.listener.write().unwrap().in_block(top_hash, block_hash); + } + } +} + +fn fire_events(listener: &mut Listener, imported: &base::Imported) +where + H: hash::Hash + Eq + traits::Member + Encode, // + Serialize, + R: SendRpcResponse, +{ + match *imported { + base::Imported::Ready { ref promoted, ref failed, ref removed, ref hash } => { + listener.ready(hash, None); + for f in failed { + listener.invalid(f); + } + for r in removed { + listener.dropped(&r.hash, Some(hash)); + } + for p in promoted { + listener.ready(p, None); + } + }, + base::Imported::Future { ref hash } => listener.future(hash), + } +} diff --git a/tee-worker/core-primitives/top-pool/src/watcher.rs b/tee-worker/core-primitives/top-pool/src/watcher.rs new file mode 100644 index 0000000000..e812fc9f8e --- /dev/null +++ b/tee-worker/core-primitives/top-pool/src/watcher.rs @@ -0,0 +1,150 @@ +// This file is part of Substrate. + +// Copyright (C) 2018-2020 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +//! Extrinsics status updates. + +extern crate alloc; +use alloc::{string::String, sync::Arc, vec::Vec}; +use codec::Encode; +use itc_direct_rpc_server::SendRpcResponse; +use itp_types::{BlockHash as SidechainBlockHash, TrustedOperationStatus}; +use log::*; +use sp_runtime::traits; +use std::hash; + +/// Extrinsic watcher. +/// +/// Represents a stream of status updates for particular extrinsic. +#[derive(Debug)] +pub struct Watcher { + //receiver: TracingUnboundedReceiver>, + hash: H, + is_in_block: bool, + rpc_response_sender: Arc, +} + +impl Watcher +where + H: hash::Hash + Encode + traits::Member, + S: SendRpcResponse, +{ + /// Returns the operation hash. + pub fn hash(&self) -> &H { + &self.hash + } + + pub fn new_watcher(hash: H, rpc_response_sender: Arc) -> Self { + Watcher { hash, is_in_block: false, rpc_response_sender } + } + + /// TrustedOperation became ready. + pub fn ready(&mut self) { + self.send(TrustedOperationStatus::Ready) + } + + /// TrustedOperation was moved to future. + pub fn future(&mut self) { + self.send(TrustedOperationStatus::Future) + } + + /// Some state change (perhaps another extrinsic was included) rendered this extrinsic invalid. + pub fn usurped(&mut self) { + //self.send(TrustedOperationStatus::Usurped(hash)); + self.send(TrustedOperationStatus::Usurped); + self.is_in_block = true; + } + + /// Extrinsic has been included in block with given hash. + pub fn in_block(&mut self, block_hash: SidechainBlockHash) { + self.send(TrustedOperationStatus::InSidechainBlock(block_hash)); + self.is_in_block = true; + } + + /// Extrinsic has been finalized by a finality gadget. + pub fn finalized(&mut self) { + //self.send(TrustedOperationStatus::Finalized(hash)); + self.send(TrustedOperationStatus::Finalized); + self.is_in_block = true; + } + + /// The block this extrinsic was included in has been retracted + pub fn finality_timeout(&mut self) { + //self.send(TrustedOperationStatus::FinalityTimeout(hash)); + self.send(TrustedOperationStatus::FinalityTimeout); + self.is_in_block = true; + } + + /// The block this extrinsic was included in has been retracted + pub fn retracted(&mut self) { + //self.send(TrustedOperationStatus::Retracted(hash)); + self.send(TrustedOperationStatus::Retracted); + } + + /// Extrinsic has been marked as invalid by the block builder. + pub fn invalid(&mut self) { + self.send(TrustedOperationStatus::Invalid); + // we mark as finalized as there are no more notifications + self.is_in_block = true; + } + + /// TrustedOperation has been dropped from the pool because of the limit. + pub fn dropped(&mut self) { + self.send(TrustedOperationStatus::Dropped); + self.is_in_block = true; + } + + /// The extrinsic has been broadcast to the given peers. + pub fn broadcast(&mut self, _peers: Vec) { + //self.send(TrustedOperationStatus::Broadcast(peers)) + self.send(TrustedOperationStatus::Broadcast) + } + + /// Returns true if the are no more listeners for this extrinsic or it was finalized. + pub fn is_done(&self) -> bool { + self.is_in_block // || self.receivers.is_empty() + } + + fn send(&mut self, status: TrustedOperationStatus) { + if let Err(e) = self.rpc_response_sender.update_status_event(self.hash().clone(), status) { + error!("failed to send status update to rpc client: {:?}", e); + } + } +} + +/* /// Sender part of the watcher. Exposed only for testing purposes. +#[derive(Debug)] +pub struct Sender { + //receivers: Vec>>, + //receivers: Vec, + is_in_block: bool, +} + */ +/* impl Default for Watcher { + fn default() -> Self { + Watcher { + //receivers: Default::default(), + hash: , + is_in_block: false, + } + } +} */ + +/* impl Sender { + /// Add a new watcher to this sender object. + +} */ diff --git a/tee-worker/core-primitives/types/Cargo.toml b/tee-worker/core-primitives/types/Cargo.toml new file mode 100644 index 0000000000..05ba6e4b29 --- /dev/null +++ b/tee-worker/core-primitives/types/Cargo.toml @@ -0,0 +1,43 @@ +[package] +authors = ["Integritee AG "] +edition = "2021" +homepage = "https://integritee.network/" +license = "Apache-2.0" +name = "itp-types" +repository = "https://github.com/integritee-network/pallets/" +version = "0.9.0" + +[dependencies] +chrono = { version = "0.4.19", default-features = false, features = ["alloc"] } +codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } +primitive-types = { version = "0.11.1", default-features = false, features = ["codec"] } +serde = { version = "1.0", default-features = false, features = ["alloc", "derive"] } +serde_json = { version = "1.0", default-features = false, features = ["alloc"] } + +# local dependencies +itp-sgx-runtime-primitives = { path = "../../core-primitives/sgx-runtime-primitives", default-features = false } + +# substrate-deps +frame-system = { default-features = false, git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.29" } +pallet-balances = { default-features = false, git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.29" } +sp-core = { default-features = false, features = ["full_crypto"], git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.29" } +sp-runtime = { default-features = false, git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.29" } +sp-std = { default-features = false, git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.29" } + +[features] +default = ["std"] +std = [ + "codec/std", + "chrono/std", + "serde/std", + "serde_json/std", + "primitive-types/std", + "itp-sgx-runtime-primitives/std", + # substrate + "frame-system/std", + "pallet-balances/std", + "sp-std/std", + "sp-core/std", + "sp-runtime/std", +] +test = [] diff --git a/tee-worker/core-primitives/types/src/lib.rs b/tee-worker/core-primitives/types/src/lib.rs new file mode 100644 index 0000000000..14b50b726e --- /dev/null +++ b/tee-worker/core-primitives/types/src/lib.rs @@ -0,0 +1,158 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +#![cfg_attr(all(not(target_env = "sgx"), not(feature = "std")), no_std)] +#![cfg_attr(target_env = "sgx", feature(rustc_private))] + +use crate::storage::StorageEntry; +use codec::{Decode, Encode}; +#[cfg(feature = "sgx")] +use sgx_tstd as std; +use sp_std::vec::Vec; + +pub mod storage; + +/// Substrate runtimes provide no string type. Hence, for arbitrary data of varying length the +/// `Vec` is used. In the polkadot-js the typedef `Text` is used to automatically +/// utf8 decode bytes into a string. +#[cfg(not(feature = "std"))] +pub type PalletString = Vec; + +#[cfg(feature = "std")] +pub type PalletString = String; + +pub use sp_core::{crypto::AccountId32 as AccountId, H256}; + +pub use itp_sgx_runtime_primitives::types::*; + +pub type IpfsHash = [u8; 46]; +pub type MrEnclave = [u8; 32]; + +pub type ConfirmCallFn = ([u8; 2], ShardIdentifier, H256, Vec); +pub type ShieldFundsFn = ([u8; 2], Vec, Balance, ShardIdentifier); +pub type CallWorkerFn = ([u8; 2], Request); + +pub type Enclave = EnclaveGen; + +/// Simple blob to hold an encoded call +#[derive(Debug, PartialEq, Eq, Clone, Default)] +pub struct OpaqueCall(pub Vec); + +impl OpaqueCall { + /// Convert call tuple to an `OpaqueCall`. + pub fn from_tuple(call: &C) -> Self { + OpaqueCall(call.encode()) + } +} + +impl Encode for OpaqueCall { + fn encode(&self) -> Vec { + self.0.clone() + } +} + +// Note in the pallet teerex this is a struct. But for the codec this does not matter. +#[derive(Encode, Decode, Default, Clone, PartialEq, Eq, Debug)] +pub struct Request { + pub shard: ShardIdentifier, + pub cyphertext: Vec, +} + +// Todo: move this improved enclave definition into a primitives crate in the pallet_teerex repo. +#[derive(Encode, Decode, Clone, PartialEq, sp_core::RuntimeDebug)] +pub struct EnclaveGen { + pub pubkey: AccountId, + // FIXME: this is redundant information + pub mr_enclave: [u8; 32], + pub timestamp: u64, + // unix epoch in milliseconds + pub url: PalletString, // utf8 encoded url +} + +impl EnclaveGen { + pub fn new(pubkey: AccountId, mr_enclave: [u8; 32], timestamp: u64, url: PalletString) -> Self { + Self { pubkey, mr_enclave, timestamp, url } + } +} + +#[derive(Debug, Clone, PartialEq, Encode, Decode)] +pub enum DirectRequestStatus { + /// Direct request was successfully executed + Ok, + /// Trusted Call Status + TrustedOperationStatus(TrustedOperationStatus), + /// Direct request could not be executed + Error, +} + +#[derive(Debug, Clone, PartialEq, Encode, Decode)] +pub enum TrustedOperationStatus { + /// TrustedOperation is submitted to the top pool. + Submitted, + /// TrustedOperation is part of the future queue. + Future, + /// TrustedOperation is part of the ready queue. + Ready, + /// The operation has been broadcast to the given peers. + Broadcast, + /// TrustedOperation has been included in block with given hash. + InSidechainBlock(BlockHash), + /// The block this operation was included in has been retracted. + Retracted, + /// Maximum number of finality watchers has been reached, + /// old watchers are being removed. + FinalityTimeout, + /// TrustedOperation has been finalized by a finality-gadget, e.g GRANDPA + Finalized, + /// TrustedOperation has been replaced in the pool, by another operation + /// that provides the same tags. (e.g. same (sender, nonce)). + Usurped, + /// TrustedOperation has been dropped from the pool because of the limit. + Dropped, + /// TrustedOperation is no longer valid in the current state. + Invalid, +} + +#[derive(Encode, Decode, Clone, Debug, PartialEq)] +pub enum WorkerRequest { + ChainStorage(Vec, Option), // (storage_key, at_block) +} + +#[derive(Encode, Decode, Clone, Debug, PartialEq)] +pub enum WorkerResponse { + ChainStorage(Vec, Option, Option>>), // (storage_key, storage_value, storage_proof) +} + +impl From>> for StorageEntry> { + fn from(response: WorkerResponse>) -> Self { + match response { + WorkerResponse::ChainStorage(key, value, proof) => StorageEntry { key, value, proof }, + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn opaque_call_encodes_correctly() { + let call_tuple = ([1u8, 2u8], 5u8); + let call = OpaqueCall::from_tuple(&call_tuple); + assert_eq!(call.encode(), call_tuple.encode()) + } +} diff --git a/tee-worker/core-primitives/types/src/storage.rs b/tee-worker/core-primitives/types/src/storage.rs new file mode 100644 index 0000000000..ea362dff8d --- /dev/null +++ b/tee-worker/core-primitives/types/src/storage.rs @@ -0,0 +1,59 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +use codec::{Decode, Encode}; +use sp_std::prelude::Vec; + +#[derive(Default, Clone, Encode, Decode)] +pub struct StorageEntry { + pub key: Vec, + pub value: Option, + pub proof: Option>>, +} + +/// Contains private fields. We don't expose a public constructor. Hence, the only way +/// to get a `StorageEntryVerified` is via the `VerifyStorageProof` trait. +#[derive(Default, Clone, Encode, Decode)] +pub struct StorageEntryVerified { + pub key: Vec, + pub value: Option, +} + +#[cfg(feature = "test")] +impl StorageEntryVerified { + pub fn new(key: Vec, value: Option) -> Self { + Self { key, value } + } +} + +impl StorageEntryVerified { + pub fn key(&self) -> &[u8] { + &self.key + } + + pub fn value(&self) -> &Option { + &self.value + } + + /// Without accessing the the field directly but with getters only, we cannot partially + /// own the struct. So we can't do: `hashmap.insert(self.key(), self.value())` if the getters + /// consumed the `self`, which is needed to return owned values. Hence, we supply this method, + /// to consume `self` and be able to use the values individually. + pub fn into_tuple(self) -> (Vec, Option) { + (self.key, self.value) + } +} diff --git a/tee-worker/core-primitives/utils/Cargo.toml b/tee-worker/core-primitives/utils/Cargo.toml new file mode 100644 index 0000000000..267b5b838e --- /dev/null +++ b/tee-worker/core-primitives/utils/Cargo.toml @@ -0,0 +1,38 @@ +[package] +authors = ["Integritee AG "] +edition = "2021" +homepage = "https://integritee.network/" +license = "Apache-2.0" +name = "itp-utils" +repository = "https://github.com/integritee-network/pallets/" +version = "0.9.0" + +[dependencies] +codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } +hex = { version = "0.4.3", default-features = false, features = ["alloc"] } + +# substrate +frame-support = { default-features = false, git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.29" } +sp-core = { default-features = false, features = ["full_crypto"], git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.29" } + +# teaclave +sgx_tstd = { branch = "master", git = "https://github.com/apache/teaclave-sgx-sdk.git", optional = true } + +# sgx enabled external libraries +thiserror_sgx = { package = "thiserror", git = "https://github.com/mesalock-linux/thiserror-sgx", tag = "sgx_1.1.3", optional = true } +# std compatible external libraries (make sure these versions match with the sgx-enabled ones above) +thiserror = { version = "1.0", optional = true } + +[features] +default = ["std"] +sgx = [ + "sgx_tstd", + "thiserror_sgx", +] +std = [ + "codec/std", + "frame-support/std", + "hex/std", + "sp-core/std", + "thiserror", +] diff --git a/tee-worker/core-primitives/utils/src/buffer.rs b/tee-worker/core-primitives/utils/src/buffer.rs new file mode 100644 index 0000000000..0a69094577 --- /dev/null +++ b/tee-worker/core-primitives/utils/src/buffer.rs @@ -0,0 +1,47 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +//! Buffer utility functions. + +use crate::error::{Error, Result}; +use frame_support::ensure; +use std::vec::Vec; + +/// Fills a given buffer with data and the left over buffer space with white spaces. +pub fn write_slice_and_whitespace_pad(writable: &mut [u8], data: Vec) -> Result<()> { + ensure!( + data.len() <= writable.len(), + Error::InsufficientBufferSize(writable.len(), data.len()) + ); + let (left, right) = writable.split_at_mut(data.len()); + left.clone_from_slice(&data); + // fill the right side with whitespace + right.iter_mut().for_each(|x| *x = 0x20); + Ok(()) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn write_slice_and_whitespace_pad_returns_error_if_buffer_too_small() { + let mut writable = vec![0; 32]; + let data = vec![1; 33]; + assert!(write_slice_and_whitespace_pad(&mut writable, data).is_err()); + } +} diff --git a/tee-worker/core-primitives/utils/src/error.rs b/tee-worker/core-primitives/utils/src/error.rs new file mode 100644 index 0000000000..662fb189a7 --- /dev/null +++ b/tee-worker/core-primitives/utils/src/error.rs @@ -0,0 +1,36 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +#[cfg(all(not(feature = "std"), feature = "sgx"))] +use crate::sgx_reexport_prelude::*; + +use std::boxed::Box; + +pub type Result = core::result::Result; + +/// extrinsics factory error +#[derive(Debug, thiserror::Error)] +pub enum Error { + #[error("Insufficient buffer size. Actual: {0}, required: {1}")] + InsufficientBufferSize(usize, usize), + #[error("Could not decode from hex data: {0}")] + Hex(hex::FromHexError), + #[error("Parity Scale Codec: {0}")] + Codec(codec::Error), + #[error(transparent)] + Other(#[from] Box), +} diff --git a/tee-worker/core-primitives/utils/src/hex.rs b/tee-worker/core-primitives/utils/src/hex.rs new file mode 100644 index 0000000000..c68ce00a29 --- /dev/null +++ b/tee-worker/core-primitives/utils/src/hex.rs @@ -0,0 +1,92 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +//! Hex encoding utility functions. + +use crate::error::{Error, Result}; +use codec::{Decode, Encode}; +use std::{string::String, vec::Vec}; + +/// Trait to encode a given value to a hex string, prefixed with "0x". +pub trait ToHexPrefixed { + fn to_hex(&self) -> String; +} + +impl ToHexPrefixed for T { + fn to_hex(&self) -> String { + hex_encode(self.encode()) + } +} + +/// Trait to decode a hex string to a given output. +pub trait FromHexPrefixed { + type Output; + + fn from_hex(msg: &str) -> Result; +} + +impl FromHexPrefixed for T { + type Output = T; + + fn from_hex(msg: &str) -> Result { + let byte_array = decode_hex(msg)?; + Decode::decode(&mut byte_array.as_slice()).map_err(Error::Codec) + } +} + +/// Hex encodes given data and preappends a "0x". +pub fn hex_encode(data: Vec) -> String { + let mut hex_str = hex::encode(data); + hex_str.insert_str(0, "0x"); + hex_str +} + +/// Helper method for decoding hex. +pub fn decode_hex>(message: T) -> Result> { + let mut message = message.as_ref(); + if message[..2] == [b'0', b'x'] { + message = &message[2..] + } + let decoded_message = hex::decode(message).map_err(Error::Hex)?; + Ok(decoded_message) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn hex_encode_decode_works() { + let data = "Hello World!".to_string(); + + let hex_encoded_data = hex_encode(data.encode()); + let decoded_data = + String::decode(&mut decode_hex(hex_encoded_data).unwrap().as_slice()).unwrap(); + + assert_eq!(data, decoded_data); + } + + #[test] + fn to_hex_from_hex_works() { + let data = "Hello World!".to_string(); + + let hex_encoded_data = data.to_hex(); + let decoded_data = String::from_hex(&hex_encoded_data).unwrap(); + + assert_eq!(data, decoded_data); + } +} diff --git a/tee-worker/core-primitives/utils/src/lib.rs b/tee-worker/core-primitives/utils/src/lib.rs new file mode 100644 index 0000000000..03f8f2d263 --- /dev/null +++ b/tee-worker/core-primitives/utils/src/lib.rs @@ -0,0 +1,44 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +//! General utility functions. + +#![cfg_attr(not(feature = "std"), no_std)] + +#[cfg(all(feature = "std", feature = "sgx"))] +compile_error!("feature \"std\" and feature \"sgx\" cannot be enabled at the same time"); + +#[cfg(all(not(feature = "std"), feature = "sgx"))] +extern crate sgx_tstd as std; + +// re-export module to properly feature gate sgx and regular std environment +#[cfg(all(not(feature = "std"), feature = "sgx"))] +pub mod sgx_reexport_prelude { + pub use thiserror_sgx as thiserror; +} + +pub mod buffer; +pub mod error; +pub mod hex; +pub mod stringify; + +// Public re-exports. +pub use self::{ + buffer::write_slice_and_whitespace_pad, + hex::{FromHexPrefixed, ToHexPrefixed}, +}; +pub use error::Error; diff --git a/tee-worker/core-primitives/utils/src/stringify.rs b/tee-worker/core-primitives/utils/src/stringify.rs new file mode 100644 index 0000000000..b03d782846 --- /dev/null +++ b/tee-worker/core-primitives/utils/src/stringify.rs @@ -0,0 +1,33 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +//! Utility methods to stringify certain types that don't have a working +//! `Debug` implementation on `sgx`. + +use codec::Encode; +use sp_core::{hexdisplay::HexDisplay, Public}; +use std::{format, string::String}; + +/// Convert a sp_core public type to string. +pub fn public_to_string(t: &T) -> String { + let crypto_pair = t.to_public_crypto_pair(); + format!("{}", HexDisplay::from(&crypto_pair.1)) +} + +pub fn account_id_to_string(account: &AccountId) -> String { + format!("{}", HexDisplay::from(&account.encode())) +} diff --git a/tee-worker/core/direct-rpc-server/Cargo.toml b/tee-worker/core/direct-rpc-server/Cargo.toml new file mode 100644 index 0000000000..9f70f1a597 --- /dev/null +++ b/tee-worker/core/direct-rpc-server/Cargo.toml @@ -0,0 +1,59 @@ +[package] +authors = ["Integritee AG "] +edition = "2021" +name = "itc-direct-rpc-server" +version = "0.9.0" + +[dependencies] +# sgx dependencies +sgx_tstd = { branch = "master", git = "https://github.com/apache/teaclave-sgx-sdk.git", optional = true, features = ["net", "thread"] } +sgx_types = { branch = "master", git = "https://github.com/apache/teaclave-sgx-sdk.git", optional = true } + +# no-std dependencies +codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } +log = { version = "0.4", default-features = false } +serde_json = { version = "1.0", default-features = false, features = ["alloc"] } +sp-runtime = { default-features = false, git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.29" } + +# local +itc-tls-websocket-server = { path = "../tls-websocket-server", default-features = false } +itp-rpc = { path = "../../core-primitives/rpc", default-features = false } +itp-types = { default-features = false, path = "../../core-primitives/types" } +itp-utils = { default-features = false, path = "../../core-primitives/utils" } + +# sgx enabled external libraries +jsonrpc-core_sgx = { package = "jsonrpc-core", git = "https://github.com/scs/jsonrpc", branch = "no_std_v18", default-features = false, optional = true } +thiserror_sgx = { package = "thiserror", git = "https://github.com/mesalock-linux/thiserror-sgx", tag = "sgx_1.1.3", optional = true } + +# std compatible external libraries (make sure these versions match with the sgx-enabled ones above) +jsonrpc-core = { version = "18", optional = true } +thiserror = { version = "1.0", optional = true } + +[features] +default = ["std"] +mocks = [] +sgx = [ + "itc-tls-websocket-server/sgx", + "itp-rpc/sgx", + "itp-utils/sgx", + "jsonrpc-core_sgx", + "sgx_tstd", + "sgx_types", + "thiserror_sgx", +] +std = [ + # no-std dependencies + "codec/std", + "log/std", + "serde_json/std", + "sp-runtime/std", + # integritee dependencies + "itp-types/std", + "itp-utils/std", + # local + "itc-tls-websocket-server/std", + "itp-rpc/std", + # optional ones + "jsonrpc-core", + "thiserror", +] diff --git a/tee-worker/core/direct-rpc-server/src/builders/mod.rs b/tee-worker/core/direct-rpc-server/src/builders/mod.rs new file mode 100644 index 0000000000..ea028434c4 --- /dev/null +++ b/tee-worker/core/direct-rpc-server/src/builders/mod.rs @@ -0,0 +1,19 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +pub mod rpc_response_builder; +pub mod rpc_return_value_builder; diff --git a/tee-worker/core/direct-rpc-server/src/builders/rpc_response_builder.rs b/tee-worker/core/direct-rpc-server/src/builders/rpc_response_builder.rs new file mode 100644 index 0000000000..2913ecf38e --- /dev/null +++ b/tee-worker/core/direct-rpc-server/src/builders/rpc_response_builder.rs @@ -0,0 +1,64 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +use crate::builders::rpc_return_value_builder::RpcReturnValueBuilder; +use itp_rpc::{RpcResponse, RpcReturnValue}; +use itp_utils::ToHexPrefixed; + +/// builder pattern for RpcResponse +pub struct RpcResponseBuilder { + maybe_id: Option, + maybe_json_rpc: Option, + maybe_result: Option, +} + +impl RpcResponseBuilder { + #[allow(unused)] + pub fn new() -> Self { + RpcResponseBuilder { maybe_id: None, maybe_json_rpc: None, maybe_result: None } + } + + #[allow(unused)] + pub fn with_id(mut self, id: u32) -> Self { + self.maybe_id = Some(id); + self + } + + #[allow(unused)] + pub fn with_json_rpc(mut self, json_rpc: String) -> Self { + self.maybe_json_rpc = Some(json_rpc); + self + } + + #[allow(unused)] + pub fn with_result(mut self, result: RpcReturnValue) -> Self { + self.maybe_result = Some(result); + self + } + + #[allow(unused)] + pub fn build(self) -> RpcResponse { + let id = self.maybe_id.unwrap_or(1u32); + let json_rpc = self.maybe_json_rpc.unwrap_or(String::from("json_rpc")); + let result = self + .maybe_result + .unwrap_or_else(|| RpcReturnValueBuilder::new().build()) + .to_hex(); + + RpcResponse { result, jsonrpc: json_rpc, id } + } +} diff --git a/tee-worker/core/direct-rpc-server/src/builders/rpc_return_value_builder.rs b/tee-worker/core/direct-rpc-server/src/builders/rpc_return_value_builder.rs new file mode 100644 index 0000000000..126d58e985 --- /dev/null +++ b/tee-worker/core/direct-rpc-server/src/builders/rpc_return_value_builder.rs @@ -0,0 +1,62 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +use codec::Encode; +use itp_rpc::RpcReturnValue; +use itp_types::DirectRequestStatus; +use std::{string::String, vec::Vec}; + +/// Builder pattern for a RpcReturnValue +pub struct RpcReturnValueBuilder { + maybe_do_watch: Option, + maybe_status: Option, + maybe_value: Option>, +} + +impl RpcReturnValueBuilder { + #[allow(unused)] + pub fn new() -> Self { + RpcReturnValueBuilder { maybe_do_watch: None, maybe_status: None, maybe_value: None } + } + + #[allow(unused)] + pub fn with_do_watch(mut self, do_watch: bool) -> Self { + self.maybe_do_watch = Some(do_watch); + self + } + + #[allow(unused)] + pub fn with_status(mut self, status: DirectRequestStatus) -> Self { + self.maybe_status = Some(status); + self + } + + #[allow(unused)] + pub fn with_value(mut self, value: Vec) -> Self { + self.maybe_value = Some(value); + self + } + + #[allow(unused)] + pub fn build(self) -> RpcReturnValue { + let do_watch = self.maybe_do_watch.unwrap_or(false); + let status = self.maybe_status.unwrap_or(DirectRequestStatus::Ok); + let value = self.maybe_value.unwrap_or(String::from("value").encode()); + + RpcReturnValue { value, do_watch, status } + } +} diff --git a/tee-worker/core/direct-rpc-server/src/lib.rs b/tee-worker/core/direct-rpc-server/src/lib.rs new file mode 100644 index 0000000000..be2c4ba7a5 --- /dev/null +++ b/tee-worker/core/direct-rpc-server/src/lib.rs @@ -0,0 +1,115 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ +#![cfg_attr(not(feature = "std"), no_std)] +#![cfg_attr(test, feature(assert_matches))] + +#[cfg(all(feature = "std", feature = "sgx"))] +compile_error!("feature \"std\" and feature \"sgx\" cannot be enabled at the same time"); + +#[cfg(all(not(feature = "std"), feature = "sgx"))] +extern crate sgx_tstd as std; + +// re-export module to properly feature gate sgx and regular std environment +#[cfg(all(not(feature = "std"), feature = "sgx"))] +pub mod sgx_reexport_prelude { + pub use jsonrpc_core_sgx as jsonrpc_core; + pub use thiserror_sgx as thiserror; +} + +#[cfg(all(not(feature = "std"), feature = "sgx"))] +use crate::sgx_reexport_prelude::*; + +use crate::rpc_watch_extractor::RpcWatchExtractor; +use codec::{Encode, Error as CodecError}; +use itc_tls_websocket_server::error::WebSocketError; +use itp_rpc::RpcResponse; +use itp_types::TrustedOperationStatus; +use serde_json::error::Error as SerdeJsonError; +use sp_runtime::traits; +use std::{boxed::Box, fmt::Debug, vec::Vec}; + +#[cfg(any(test, feature = "mocks"))] +pub mod mocks; + +#[cfg(test)] +mod builders; + +pub mod response_channel; +pub mod rpc_connection_registry; +pub mod rpc_responder; +pub mod rpc_watch_extractor; +pub mod rpc_ws_handler; + +/// General web-socket error type +#[derive(Debug, thiserror::Error)] +pub enum DirectRpcError { + #[error("Invalid connection hash")] + InvalidConnectionHash, + #[error("RPC serialization error: {0}")] + SerializationError(SerdeJsonError), + #[error("Web socket error: {0}")] + WebSocketError(#[from] WebSocketError), + #[error("Encoding error: {0}")] + EncodingError(CodecError), + #[error("Other error: {0}")] + Other(Box), +} + +pub type DirectRpcResult = Result; + +/// trait helper to mix-in all necessary traits for a hash +pub trait RpcHash: std::hash::Hash + traits::Member + Encode {} +impl RpcHash for T {} + +/// Registry for RPC connections (i.e. connections that are kept alive to send updates). +pub trait RpcConnectionRegistry: Send + Sync { + type Hash: RpcHash; + type Connection: Copy + Debug; + + fn store(&self, hash: Self::Hash, connection: Self::Connection, rpc_response: RpcResponse); + + fn withdraw(&self, hash: &Self::Hash) -> Option<(Self::Connection, RpcResponse)>; +} + +/// Sends an RPC response back to the client. +pub trait SendRpcResponse: Send + Sync { + type Hash: RpcHash; + + fn update_status_event( + &self, + hash: Self::Hash, + status_update: TrustedOperationStatus, + ) -> DirectRpcResult<()>; + + fn send_state(&self, hash: Self::Hash, state_encoded: Vec) -> DirectRpcResult<()>; +} + +/// Determines if a given connection must be watched (i.e. kept alive), +/// based on the information in the RpcResponse. +pub trait DetermineWatch: Send + Sync { + type Hash: RpcHash; + + fn must_be_watched(&self, rpc_response: &RpcResponse) -> DirectRpcResult>; +} + +/// Convenience method to create a do_watch extractor. +pub fn create_determine_watch() -> RpcWatchExtractor +where + Hash: RpcHash, +{ + RpcWatchExtractor::::new() +} diff --git a/tee-worker/core/direct-rpc-server/src/mocks/determine_watch_mock.rs b/tee-worker/core/direct-rpc-server/src/mocks/determine_watch_mock.rs new file mode 100644 index 0000000000..c01730390d --- /dev/null +++ b/tee-worker/core/direct-rpc-server/src/mocks/determine_watch_mock.rs @@ -0,0 +1,52 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +use crate::{DetermineWatch, DirectRpcResult, RpcHash}; +use itp_rpc::RpcResponse; + +pub struct DetermineWatchMock +where + Hash: RpcHash, +{ + watch_next: Option, +} + +impl DetermineWatchMock +where + Hash: RpcHash, +{ + #[allow(unused)] + pub fn do_watch(hash: Hash) -> Self { + DetermineWatchMock { watch_next: Some(hash) } + } + + #[allow(unused)] + pub fn no_watch() -> Self { + DetermineWatchMock { watch_next: None } + } +} + +impl DetermineWatch for DetermineWatchMock +where + Hash: RpcHash, +{ + type Hash = Hash; + + fn must_be_watched(&self, _rpc_response: &RpcResponse) -> DirectRpcResult> { + Ok(self.watch_next.clone()) + } +} diff --git a/tee-worker/core/direct-rpc-server/src/mocks/mod.rs b/tee-worker/core/direct-rpc-server/src/mocks/mod.rs new file mode 100644 index 0000000000..011b4d9905 --- /dev/null +++ b/tee-worker/core/direct-rpc-server/src/mocks/mod.rs @@ -0,0 +1,20 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +pub mod determine_watch_mock; +pub mod response_channel_mock; +pub mod send_rpc_response_mock; diff --git a/tee-worker/core/direct-rpc-server/src/mocks/response_channel_mock.rs b/tee-worker/core/direct-rpc-server/src/mocks/response_channel_mock.rs new file mode 100644 index 0000000000..6a612d6766 --- /dev/null +++ b/tee-worker/core/direct-rpc-server/src/mocks/response_channel_mock.rs @@ -0,0 +1,55 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +#[cfg(feature = "sgx")] +use std::sync::SgxRwLock as RwLock; + +#[cfg(feature = "std")] +use std::sync::RwLock; + +use crate::{response_channel::ResponseChannel, DirectRpcError}; +use std::vec::Vec; + +#[derive(Default)] +pub struct ResponseChannelMock +where + Token: Copy + Send + Sync, +{ + sent_messages: RwLock>, +} + +impl ResponseChannelMock +where + Token: Copy + Send + Sync, +{ + pub fn number_of_updates(&self) -> usize { + self.sent_messages.read().unwrap().len() + } +} + +impl ResponseChannel for ResponseChannelMock +where + Token: Copy + Send + Sync, +{ + type Error = DirectRpcError; + + fn respond(&self, token: Token, message: String) -> Result<(), Self::Error> { + let mut messages_lock = self.sent_messages.write().unwrap(); + messages_lock.push((token, message)); + Ok(()) + } +} diff --git a/tee-worker/core/direct-rpc-server/src/mocks/send_rpc_response_mock.rs b/tee-worker/core/direct-rpc-server/src/mocks/send_rpc_response_mock.rs new file mode 100644 index 0000000000..fdcec38f2f --- /dev/null +++ b/tee-worker/core/direct-rpc-server/src/mocks/send_rpc_response_mock.rs @@ -0,0 +1,53 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +#[cfg(feature = "sgx")] +use std::sync::SgxRwLock as RwLock; + +#[cfg(feature = "std")] +use std::sync::RwLock; + +use crate::{DirectRpcResult, RpcHash, SendRpcResponse}; +use itp_types::TrustedOperationStatus; +use std::vec::Vec; + +/// Send RPC response mock. +#[derive(Default)] +pub struct SendRpcResponseMock { + pub sent_states: RwLock)>>, +} + +impl SendRpcResponse for SendRpcResponseMock +where + HashType: RpcHash, +{ + type Hash = HashType; + + fn update_status_event( + &self, + _hash: Self::Hash, + _status_update: TrustedOperationStatus, + ) -> DirectRpcResult<()> { + unimplemented!() + } + + fn send_state(&self, hash: Self::Hash, state_encoded: Vec) -> DirectRpcResult<()> { + let mut states_lock = self.sent_states.write().unwrap(); + states_lock.push((hash, state_encoded)); + Ok(()) + } +} diff --git a/tee-worker/core/direct-rpc-server/src/response_channel.rs b/tee-worker/core/direct-rpc-server/src/response_channel.rs new file mode 100644 index 0000000000..b1fe6a3fea --- /dev/null +++ b/tee-worker/core/direct-rpc-server/src/response_channel.rs @@ -0,0 +1,26 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +use crate::DirectRpcError; +use std::string::String; + +/// Response / status update channel for an RPC call. +pub trait ResponseChannel: Send + Sync { + type Error: Into; + + fn respond(&self, token: Token, message: String) -> Result<(), Self::Error>; +} diff --git a/tee-worker/core/direct-rpc-server/src/rpc_connection_registry.rs b/tee-worker/core/direct-rpc-server/src/rpc_connection_registry.rs new file mode 100644 index 0000000000..e4586dc29f --- /dev/null +++ b/tee-worker/core/direct-rpc-server/src/rpc_connection_registry.rs @@ -0,0 +1,124 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +#[cfg(all(not(feature = "std"), feature = "sgx"))] +use std::sync::SgxRwLock as RwLock; + +#[cfg(feature = "std")] +use std::sync::RwLock; + +use crate::{RpcConnectionRegistry, RpcHash}; +use itp_rpc::RpcResponse; +use std::{collections::HashMap, fmt::Debug}; + +type HashMapLock = RwLock>; + +pub struct ConnectionRegistry +where + Hash: RpcHash, + Token: Copy + Send + Sync + Debug, +{ + connection_map: HashMapLock<::Hash, (Token, RpcResponse)>, +} + +impl ConnectionRegistry +where + Hash: RpcHash, + Token: Copy + Send + Sync + Debug, +{ + pub fn new() -> Self { + Self::default() + } + + #[cfg(test)] + pub fn is_empty(&self) -> bool { + self.connection_map.read().unwrap().is_empty() + } +} + +impl Default for ConnectionRegistry +where + Hash: RpcHash, + Token: Copy + Send + Sync + Debug, +{ + fn default() -> Self { + ConnectionRegistry { connection_map: RwLock::new(HashMap::default()) } + } +} + +impl RpcConnectionRegistry for ConnectionRegistry +where + Hash: RpcHash, + Token: Copy + Send + Sync + Debug, +{ + type Hash = Hash; + type Connection = Token; + + fn store(&self, hash: Self::Hash, connection: Self::Connection, rpc_response: RpcResponse) { + let mut map = self.connection_map.write().expect("Lock poisoning"); + map.insert(hash, (connection, rpc_response)); + } + + fn withdraw(&self, hash: &Self::Hash) -> Option<(Self::Connection, RpcResponse)> { + let mut map = self.connection_map.write().expect("Lock poisoning"); + map.remove(hash) + } +} + +#[cfg(test)] +pub mod tests { + use super::*; + + type TestRegistry = ConnectionRegistry; + + #[test] + pub fn adding_element_with_same_hash_overwrite() { + let registry = TestRegistry::new(); + + let hash = "first".to_string(); + + registry.store(hash.clone(), 1, dummy_rpc_response()); + registry.store(hash.clone(), 2, dummy_rpc_response()); + + let connection_token = registry.withdraw(&hash).unwrap().0; + assert_eq!(2, connection_token); + } + + #[test] + pub fn withdrawing_from_empty_registry_returns_none() { + let registry = TestRegistry::new(); + + assert!(registry.withdraw(&"hash".to_string()).is_none()); + } + + #[test] + pub fn withdrawing_only_element_clears_registry() { + let registry = TestRegistry::new(); + let hash = "first".to_string(); + + registry.store(hash.clone(), 1, dummy_rpc_response()); + + let connection = registry.withdraw(&hash); + + assert!(connection.is_some()); + assert!(registry.is_empty()); + } + + fn dummy_rpc_response() -> RpcResponse { + RpcResponse { jsonrpc: String::new(), result: Default::default(), id: 1u32 } + } +} diff --git a/tee-worker/core/direct-rpc-server/src/rpc_responder.rs b/tee-worker/core/direct-rpc-server/src/rpc_responder.rs new file mode 100644 index 0000000000..d723ebdab5 --- /dev/null +++ b/tee-worker/core/direct-rpc-server/src/rpc_responder.rs @@ -0,0 +1,272 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +use crate::{ + response_channel::ResponseChannel, DirectRpcError, DirectRpcResult, RpcConnectionRegistry, + RpcHash, SendRpcResponse, +}; +use itp_rpc::{RpcResponse, RpcReturnValue}; +use itp_types::{DirectRequestStatus, TrustedOperationStatus}; +use itp_utils::{FromHexPrefixed, ToHexPrefixed}; +use log::*; +use std::{boxed::Box, sync::Arc, vec::Vec}; + +pub struct RpcResponder +where + Registry: RpcConnectionRegistry, + Hash: RpcHash, + ResponseChannelType: ResponseChannel, +{ + connection_registry: Arc, + response_channel: Arc, +} + +impl RpcResponder +where + Registry: RpcConnectionRegistry, + Hash: RpcHash, + ResponseChannelType: ResponseChannel, +{ + pub fn new( + connection_registry: Arc, + web_socket_responder: Arc, + ) -> Self { + RpcResponder { connection_registry, response_channel: web_socket_responder } + } + + fn encode_and_send_response( + &self, + connection: Registry::Connection, + rpc_response: &RpcResponse, + ) -> DirectRpcResult<()> { + let string_response = + serde_json::to_string(&rpc_response).map_err(DirectRpcError::SerializationError)?; + + self.response_channel.respond(connection, string_response).map_err(|e| e.into()) + } +} + +impl SendRpcResponse + for RpcResponder +where + Registry: RpcConnectionRegistry, + Hash: RpcHash, + ResponseChannelType: ResponseChannel, +{ + type Hash = Hash; + + fn update_status_event( + &self, + hash: Hash, + status_update: TrustedOperationStatus, + ) -> DirectRpcResult<()> { + debug!("updating status event"); + + // withdraw removes it from the registry + let (connection_token, rpc_response) = self + .connection_registry + .withdraw(&hash) + .ok_or(DirectRpcError::InvalidConnectionHash)?; + + let mut new_response = rpc_response.clone(); + + let mut result = RpcReturnValue::from_hex(&rpc_response.result) + .map_err(|e| DirectRpcError::Other(Box::new(e)))?; + + let do_watch = continue_watching(&status_update); + + // update response + result.do_watch = do_watch; + result.status = DirectRequestStatus::TrustedOperationStatus(status_update); + new_response.result = result.to_hex(); + + self.encode_and_send_response(connection_token, &new_response)?; + + if do_watch { + self.connection_registry.store(hash, connection_token, new_response); + } + + debug!("updating status event successful"); + Ok(()) + } + + fn send_state(&self, hash: Hash, state_encoded: Vec) -> DirectRpcResult<()> { + debug!("sending state"); + + // withdraw removes it from the registry + let (connection_token, mut response) = self + .connection_registry + .withdraw(&hash) + .ok_or(DirectRpcError::InvalidConnectionHash)?; + + // create return value + // TODO: Signature? + let submitted = + DirectRequestStatus::TrustedOperationStatus(TrustedOperationStatus::Submitted); + let result = RpcReturnValue::new(state_encoded, false, submitted); + + // update response + response.result = result.to_hex(); + + self.encode_and_send_response(connection_token, &response)?; + + debug!("sending state successful"); + Ok(()) + } +} + +fn continue_watching(status: &TrustedOperationStatus) -> bool { + !matches!( + status, + TrustedOperationStatus::Invalid + | TrustedOperationStatus::InSidechainBlock(_) + | TrustedOperationStatus::Finalized + | TrustedOperationStatus::Usurped + ) +} + +#[cfg(test)] +pub mod tests { + + use super::*; + use crate::{ + builders::rpc_response_builder::RpcResponseBuilder, + mocks::response_channel_mock::ResponseChannelMock, + rpc_connection_registry::ConnectionRegistry, + }; + use codec::Encode; + use std::assert_matches::assert_matches; + + type TestConnectionToken = u64; + type TestResponseChannel = ResponseChannelMock; + type TestConnectionRegistry = ConnectionRegistry; + + #[test] + fn given_empty_registry_when_updating_status_event_then_return_error() { + let connection_registry = Arc::new(TestConnectionRegistry::new()); + let websocket_responder = Arc::new(TestResponseChannel::default()); + let rpc_responder = RpcResponder::new(connection_registry, websocket_responder); + + assert_matches!( + rpc_responder + .update_status_event("hash".to_string(), TrustedOperationStatus::Broadcast), + Err(DirectRpcError::InvalidConnectionHash) + ); + } + + #[test] + fn given_empty_registry_when_sending_state_then_return_error() { + let connection_registry = Arc::new(TestConnectionRegistry::new()); + let websocket_responder = Arc::new(TestResponseChannel::default()); + let rpc_responder = RpcResponder::new(connection_registry, websocket_responder); + + assert_matches!( + rpc_responder.send_state("hash".to_string(), vec![1u8, 2u8]), + Err(DirectRpcError::InvalidConnectionHash) + ); + } + + #[test] + fn updating_status_event_with_finalized_state_removes_connection() { + let connection_hash = String::from("conn_hash"); + let connection_registry = create_registry_with_single_connection(connection_hash.clone()); + + let websocket_responder = Arc::new(TestResponseChannel::default()); + let rpc_responder = + RpcResponder::new(connection_registry.clone(), websocket_responder.clone()); + + let result = rpc_responder + .update_status_event(connection_hash.clone(), TrustedOperationStatus::Finalized); + + assert!(result.is_ok()); + + verify_closed_connection(&connection_hash, connection_registry); + assert_eq!(1, websocket_responder.number_of_updates()); + } + + #[test] + fn updating_status_event_with_ready_state_keeps_connection_and_sends_update() { + let connection_hash = String::from("conn_hash"); + let connection_registry = create_registry_with_single_connection(connection_hash.clone()); + + let websocket_responder = Arc::new(TestResponseChannel::default()); + let rpc_responder = + RpcResponder::new(connection_registry.clone(), websocket_responder.clone()); + + let first_result = rpc_responder + .update_status_event(connection_hash.clone(), TrustedOperationStatus::Ready); + + let second_result = rpc_responder + .update_status_event(connection_hash.clone(), TrustedOperationStatus::Submitted); + + assert!(first_result.is_ok()); + assert!(second_result.is_ok()); + + verify_open_connection(&connection_hash, connection_registry); + assert_eq!(2, websocket_responder.number_of_updates()); + } + + #[test] + fn sending_state_successfully_sends_update_and_removes_connection_token() { + let connection_hash = String::from("conn_hash"); + let connection_registry = create_registry_with_single_connection(connection_hash.clone()); + + let websocket_responder = Arc::new(TestResponseChannel::default()); + let rpc_responder = + RpcResponder::new(connection_registry.clone(), websocket_responder.clone()); + + let result = rpc_responder.send_state(connection_hash.clone(), "new_state".encode()); + assert!(result.is_ok()); + + verify_closed_connection(&connection_hash, connection_registry); + assert_eq!(1, websocket_responder.number_of_updates()); + } + + #[test] + fn test_continue_watching() { + assert!(!continue_watching(&TrustedOperationStatus::Invalid)); + assert!(!continue_watching(&TrustedOperationStatus::Usurped)); + assert!(continue_watching(&TrustedOperationStatus::Future)); + assert!(continue_watching(&TrustedOperationStatus::Broadcast)); + assert!(continue_watching(&TrustedOperationStatus::Dropped)); + } + + fn verify_open_connection( + connection_hash: &String, + connection_registry: Arc, + ) { + let maybe_connection = connection_registry.withdraw(&connection_hash); + assert!(maybe_connection.is_some()); + } + + fn verify_closed_connection( + connection_hash: &String, + connection_registry: Arc, + ) { + assert!(connection_registry.withdraw(&connection_hash).is_none()); + } + + fn create_registry_with_single_connection( + connection_hash: String, + ) -> Arc { + let connection_registry = TestConnectionRegistry::new(); + let rpc_response = RpcResponseBuilder::new().with_id(2).build(); + + connection_registry.store(connection_hash.clone(), 1, rpc_response); + Arc::new(connection_registry) + } +} diff --git a/tee-worker/core/direct-rpc-server/src/rpc_watch_extractor.rs b/tee-worker/core/direct-rpc-server/src/rpc_watch_extractor.rs new file mode 100644 index 0000000000..cad846267d --- /dev/null +++ b/tee-worker/core/direct-rpc-server/src/rpc_watch_extractor.rs @@ -0,0 +1,122 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +use crate::{DetermineWatch, DirectRpcError, DirectRpcResult, RpcHash}; +use codec::Decode; +use itp_rpc::{RpcResponse, RpcReturnValue}; +use itp_types::DirectRequestStatus; +use itp_utils::FromHexPrefixed; +use std::{boxed::Box, marker::PhantomData}; + +pub struct RpcWatchExtractor +where + Hash: RpcHash, +{ + phantom_data: PhantomData, +} + +impl RpcWatchExtractor +where + Hash: RpcHash, +{ + pub fn new() -> Self { + Self::default() + } +} + +impl Default for RpcWatchExtractor +where + Hash: RpcHash, +{ + fn default() -> Self { + RpcWatchExtractor { phantom_data: PhantomData } + } +} + +impl DetermineWatch for RpcWatchExtractor +where + Hash: RpcHash + Decode, +{ + type Hash = Hash; + + fn must_be_watched(&self, rpc_response: &RpcResponse) -> DirectRpcResult> { + let rpc_return_value = RpcReturnValue::from_hex(&rpc_response.result) + .map_err(|e| DirectRpcError::Other(Box::new(e)))?; + + if !rpc_return_value.do_watch { + return Ok(None) + } + + match rpc_return_value.status { + DirectRequestStatus::TrustedOperationStatus(_) => + Self::Hash::decode(&mut rpc_return_value.value.as_slice()) + .map(Some) + .map_err(DirectRpcError::EncodingError), + _ => Ok(None), + } + } +} + +#[cfg(test)] +pub mod tests { + + use super::*; + use crate::builders::{ + rpc_response_builder::RpcResponseBuilder, rpc_return_value_builder::RpcReturnValueBuilder, + }; + use codec::Encode; + use itp_types::TrustedOperationStatus; + + #[test] + fn invalid_rpc_response_returns_error() { + let watch_extractor = RpcWatchExtractor::::new(); + let rpc_response = + RpcResponse { id: 1u32, jsonrpc: String::from("json"), result: "hello".to_string() }; + + assert!(watch_extractor.must_be_watched(&rpc_response).is_err()); + } + + #[test] + fn rpc_response_without_watch_flag_must_not_be_watched() { + let watch_extractor = RpcWatchExtractor::::new(); + let rpc_result = RpcReturnValueBuilder::new() + .with_do_watch(false) + .with_status(DirectRequestStatus::TrustedOperationStatus(TrustedOperationStatus::Ready)) + .build(); + let rpc_response = RpcResponseBuilder::new().with_result(rpc_result).build(); + + let do_watch = watch_extractor.must_be_watched(&rpc_response).unwrap(); + + assert_eq!(None, do_watch); + } + + #[test] + fn rpc_response_with_watch_flag_must_be_watched() { + let hash = String::from("rpc_hash"); + let watch_extractor = RpcWatchExtractor::::new(); + let rpc_return_value = RpcReturnValueBuilder::new() + .with_do_watch(true) + .with_value(hash.encode()) + .with_status(DirectRequestStatus::TrustedOperationStatus(TrustedOperationStatus::Ready)) + .build(); + let rpc_response = RpcResponseBuilder::new().with_result(rpc_return_value).build(); + + let do_watch = watch_extractor.must_be_watched(&rpc_response).unwrap(); + + assert_eq!(Some(hash.clone()), do_watch); + } +} diff --git a/tee-worker/core/direct-rpc-server/src/rpc_ws_handler.rs b/tee-worker/core/direct-rpc-server/src/rpc_ws_handler.rs new file mode 100644 index 0000000000..5327da7282 --- /dev/null +++ b/tee-worker/core/direct-rpc-server/src/rpc_ws_handler.rs @@ -0,0 +1,225 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +#[cfg(all(not(feature = "std"), feature = "sgx"))] +use crate::sgx_reexport_prelude::*; + +use crate::{DetermineWatch, RpcConnectionRegistry, RpcHash}; +use itc_tls_websocket_server::{error::WebSocketResult, ConnectionToken, WebSocketMessageHandler}; +use jsonrpc_core::IoHandler; +use log::*; +use std::{string::String, sync::Arc}; + +pub struct RpcWsHandler +where + Watcher: DetermineWatch, + Registry: RpcConnectionRegistry, + Hash: RpcHash, +{ + rpc_io_handler: IoHandler, + connection_watcher: Arc, + connection_registry: Arc, +} + +impl RpcWsHandler +where + Watcher: DetermineWatch, + Registry: RpcConnectionRegistry, + Hash: RpcHash, +{ + pub fn new( + rpc_io_handler: IoHandler, + connection_watcher: Arc, + connection_registry: Arc, + ) -> Self { + RpcWsHandler { rpc_io_handler, connection_watcher, connection_registry } + } +} + +impl WebSocketMessageHandler for RpcWsHandler +where + Watcher: DetermineWatch, + Registry: RpcConnectionRegistry, + Registry::Connection: From, + Hash: RpcHash, +{ + fn handle_message( + &self, + connection_token: ConnectionToken, + message: String, + ) -> WebSocketResult> { + let maybe_rpc_response = self.rpc_io_handler.handle_request_sync(message.as_str()); + + debug!("RPC response string: {:?}", maybe_rpc_response); + + if let Ok(rpc_response) = + serde_json::from_str(maybe_rpc_response.clone().unwrap_or_default().as_str()) + { + if let Ok(Some(connection_hash)) = + self.connection_watcher.must_be_watched(&rpc_response) + { + self.connection_registry.store( + connection_hash, + connection_token.into(), + rpc_response, + ); + } + } + + Ok(maybe_rpc_response) + } +} + +#[cfg(test)] +pub mod tests { + + use super::*; + use crate::{ + mocks::determine_watch_mock::DetermineWatchMock, + rpc_connection_registry::ConnectionRegistry, + }; + use codec::Encode; + use itc_tls_websocket_server::ConnectionToken; + use itp_rpc::RpcReturnValue; + use itp_types::DirectRequestStatus; + use itp_utils::ToHexPrefixed; + use jsonrpc_core::Params; + use serde_json::json; + + type TestConnectionRegistry = ConnectionRegistry; + type TestConnectionWatcher = DetermineWatchMock; + type TestWsHandler = RpcWsHandler; + + const RPC_METHOD_NAME: &str = "test_call"; + + #[test] + fn valid_rpc_call_without_watch_runs_successfully() { + let io_handler = create_io_handler_with_method(RPC_METHOD_NAME); + + let (connection_token, message) = create_message_to_handle(RPC_METHOD_NAME); + + let (ws_handler, connection_registry) = create_ws_handler(io_handler, None); + + let handle_result = ws_handler.handle_message(connection_token, message); + + assert!(handle_result.is_ok()); + assert!(connection_registry.is_empty()); + } + + #[test] + fn valid_rpc_call_with_watch_runs_successfully_and_stores_connection() { + let io_handler = create_io_handler_with_method(RPC_METHOD_NAME); + + let connection_hash = String::from("connection_hash"); + let (connection_token, message) = create_message_to_handle(RPC_METHOD_NAME); + + let (ws_handler, connection_registry) = + create_ws_handler(io_handler, Some(connection_hash.clone())); + + let handle_result = ws_handler.handle_message(connection_token, message); + + assert!(handle_result.is_ok()); + assert!(connection_registry.withdraw(&connection_hash).is_some()); + } + + #[test] + fn when_rpc_returns_error_then_return_ok_but_status_is_set_to_error() { + let io_handler = create_io_handler_with_error(RPC_METHOD_NAME); + + let connection_hash = String::from("connection_hash"); + let (connection_token, message) = create_message_to_handle(RPC_METHOD_NAME); + + let (ws_handler, connection_registry) = + create_ws_handler(io_handler, Some(connection_hash.clone())); + + let handle_result = ws_handler.handle_message(connection_token, message); + + assert!(handle_result.is_ok()); + assert!(connection_registry.withdraw(&connection_hash).is_some()); + } + + #[test] + fn when_rpc_method_does_not_match_anything_return_json_error_message() { + let io_handler = create_io_handler_with_error(RPC_METHOD_NAME); + let (connection_token, message) = create_message_to_handle("not_a_valid_method"); + + let (ws_handler, connection_registry) = create_ws_handler(io_handler, None); + + let handle_result = ws_handler.handle_message(connection_token, message).unwrap().unwrap(); + + assert_eq!(handle_result, "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32601,\"message\":\"Method not found\"},\"id\":1}"); + assert!(connection_registry.is_empty()); + } + + fn create_message_to_handle(method_name: &str) -> (ConnectionToken, String) { + let json_rpc_pre_method = r#"{"jsonrpc": "2.0", "method": ""#; + let json_rpc_post_method = r#"", "params": {}, "id": 1}"#; + + let json_string = format!("{}{}{}", json_rpc_pre_method, method_name, json_rpc_post_method); + debug!("JSON input: {}", json_string); + + (ConnectionToken(23), json_string) + } + + fn create_ws_handler( + io_handler: IoHandler, + watch_connection: Option, + ) -> (TestWsHandler, Arc) { + let watcher = match watch_connection { + Some(hash) => TestConnectionWatcher::do_watch(hash), + None => TestConnectionWatcher::no_watch(), + }; + + let connection_registry = Arc::new(TestConnectionRegistry::new()); + + ( + TestWsHandler::new(io_handler, Arc::new(watcher), connection_registry.clone()), + connection_registry, + ) + } + + fn create_io_handler_with_method(method_name: &str) -> IoHandler { + create_io_handler( + method_name, + RpcReturnValue { + do_watch: false, + value: String::from("value").encode(), + status: DirectRequestStatus::Ok, + }, + ) + } + + fn create_io_handler_with_error(method_name: &str) -> IoHandler { + create_io_handler( + method_name, + RpcReturnValue { + value: "error!".encode(), + do_watch: false, + status: DirectRequestStatus::Error, + }, + ) + } + + fn create_io_handler(method_name: &str, return_value: ReturnValue) -> IoHandler + where + ReturnValue: Encode + Send + Sync + 'static, + { + let mut io_handler = IoHandler::new(); + io_handler.add_sync_method(method_name, move |_: Params| Ok(json!(return_value.to_hex()))); + io_handler + } +} diff --git a/tee-worker/core/offchain-worker-executor/Cargo.toml b/tee-worker/core/offchain-worker-executor/Cargo.toml new file mode 100644 index 0000000000..beda55e92b --- /dev/null +++ b/tee-worker/core/offchain-worker-executor/Cargo.toml @@ -0,0 +1,72 @@ +[package] +authors = ["Integritee AG "] +edition = "2021" +name = "itc-offchain-worker-executor" +version = "0.9.0" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +# sgx dependencies +sgx_tstd = { branch = "master", git = "https://github.com/apache/teaclave-sgx-sdk.git", optional = true } +sgx_types = { branch = "master", git = "https://github.com/apache/teaclave-sgx-sdk.git" } + +# sgx enabled external libraries +thiserror_sgx = { package = "thiserror", git = "https://github.com/mesalock-linux/thiserror-sgx", tag = "sgx_1.1.3", optional = true } + +# std compatible external libraries (make sure these versions match with the sgx-enabled ones above) +thiserror = { version = "1.0", optional = true } + +# local dependencies +ita-stf = { path = "../../app-libs/stf", default-features = false } +itc-parentchain-light-client = { path = "../../core/parentchain/light-client", default-features = false } +itp-extrinsics-factory = { path = "../../core-primitives/extrinsics-factory", default-features = false } +itp-stf-executor = { path = "../../core-primitives/stf-executor", default-features = false } +itp-stf-interface = { path = "../../core-primitives/stf-interface", default-features = false } +itp-stf-state-handler = { path = "../../core-primitives/stf-state-handler", default-features = false } +itp-top-pool-author = { path = "../../core-primitives/top-pool-author", default-features = false } +itp-types = { path = "../../core-primitives/types", default-features = false } + +# Substrate dependencies +sp-core = { default-features = false, features = ["full_crypto"], git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.29" } +sp-runtime = { default-features = false, git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.29" } + +# no-std compatible libraries +log = { version = "0.4", default-features = false } + +[dev-dependencies] +codec = { package = "parity-scale-codec", version = "3.0.0", features = ["derive"] } +ita-stf = { path = "../../app-libs/stf" } +itc-parentchain-light-client = { path = "../../core/parentchain/light-client", features = ["mocks"] } +itp-extrinsics-factory = { path = "../../core-primitives/extrinsics-factory", features = ["mocks"] } +itp-sgx-externalities = { path = "../../core-primitives/substrate-sgx/externalities" } +itp-stf-executor = { path = "../../core-primitives/stf-executor", features = ["mocks"] } +itp-stf-interface = { path = "../../core-primitives/stf-interface", features = ["mocks"] } +itp-test = { path = "../../core-primitives/test" } +itp-top-pool-author = { path = "../../core-primitives/top-pool-author", features = ["mocks"] } +sp-io = { path = "../../core-primitives/substrate-sgx/sp-io" } + +[features] +default = ["std"] +sgx = [ + "ita-stf/sgx", + "itc-parentchain-light-client/sgx", + "itp-extrinsics-factory/sgx", + "itp-stf-executor/sgx", + "itp-stf-state-handler/sgx", + "itp-top-pool-author/sgx", + "sgx_tstd", + "thiserror_sgx", +] +std = [ + "ita-stf/std", + "itc-parentchain-light-client/std", + "itp-extrinsics-factory/std", + "itp-stf-executor/std", + "itp-stf-interface/std", + "itp-stf-state-handler/std", + "itp-top-pool-author/std", + "sp-core/std", + "sp-runtime/std", + "thiserror", +] diff --git a/tee-worker/core/offchain-worker-executor/src/error.rs b/tee-worker/core/offchain-worker-executor/src/error.rs new file mode 100644 index 0000000000..2c955d3e00 --- /dev/null +++ b/tee-worker/core/offchain-worker-executor/src/error.rs @@ -0,0 +1,40 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +#[cfg(all(not(feature = "std"), feature = "sgx"))] +use crate::sgx_reexport_prelude::*; + +use std::boxed::Box; + +pub type Result = core::result::Result; + +/// General offchain-worker error type +#[derive(Debug, thiserror::Error)] +pub enum Error { + #[error("STF state handler error: {0}")] + StfStateHandler(#[from] itp_stf_state_handler::error::Error), + #[error("STF executor error: {0}")] + StfExecutor(#[from] itp_stf_executor::error::Error), + #[error("TOP pool author error: {0}")] + TopPoolAuthor(#[from] itp_top_pool_author::error::Error), + #[error("Light-client error: {0}")] + LightClient(#[from] itc_parentchain_light_client::error::Error), + #[error("Extrinsics factory error: {0}")] + ExtrinsicsFactory(#[from] itp_extrinsics_factory::error::Error), + #[error("{0}")] + Other(Box), +} diff --git a/tee-worker/core/offchain-worker-executor/src/executor.rs b/tee-worker/core/offchain-worker-executor/src/executor.rs new file mode 100644 index 0000000000..aad974ce91 --- /dev/null +++ b/tee-worker/core/offchain-worker-executor/src/executor.rs @@ -0,0 +1,337 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +use crate::error::Result; +use ita_stf::hash::TrustedOperationOrHash; +use itc_parentchain_light_client::{ + concurrent_access::ValidatorAccess, BlockNumberOps, ExtrinsicSender, LightClientState, + NumberFor, +}; +use itp_extrinsics_factory::CreateExtrinsics; +use itp_stf_executor::{traits::StateUpdateProposer, ExecutedOperation}; +use itp_stf_interface::system_pallet::SystemPalletEventInterface; +use itp_stf_state_handler::{handle_state::HandleState, query_shard_state::QueryShardState}; +use itp_top_pool_author::traits::AuthorApi; +use itp_types::{OpaqueCall, ShardIdentifier, H256}; +use log::*; +use sp_runtime::traits::Block; +use std::{marker::PhantomData, sync::Arc, time::Duration, vec::Vec}; + +/// Off-chain worker executor implementation. +/// +/// Executes calls found in the top-pool and immediately applies the corresponding state diffs. +/// - Sends confirmations for all executed calls (TODO) +/// - Sends extrinsics for any parentchain effects (such as unshield calls). +/// +/// The trigger to start executing calls is given when the parentchain block imported event is +/// signaled (event listener). +pub struct Executor< + ParentchainBlock, + TopPoolAuthor, + StfExecutor, + StateHandler, + ValidatorAccessor, + ExtrinsicsFactory, + Stf, +> { + top_pool_author: Arc, + stf_executor: Arc, + state_handler: Arc, + validator_accessor: Arc, + extrinsics_factory: Arc, + _phantom: PhantomData<(ParentchainBlock, Stf)>, +} + +impl< + ParentchainBlock, + TopPoolAuthor, + StfExecutor, + StateHandler, + ValidatorAccessor, + ExtrinsicsFactory, + Stf, + > + Executor< + ParentchainBlock, + TopPoolAuthor, + StfExecutor, + StateHandler, + ValidatorAccessor, + ExtrinsicsFactory, + Stf, + > where + ParentchainBlock: Block, + StfExecutor: StateUpdateProposer, + TopPoolAuthor: AuthorApi, + StateHandler: QueryShardState + HandleState, + ValidatorAccessor: ValidatorAccess + Send + Sync + 'static, + ExtrinsicsFactory: CreateExtrinsics, + NumberFor: BlockNumberOps, + Stf: SystemPalletEventInterface, +{ + pub fn new( + top_pool_author: Arc, + stf_executor: Arc, + state_handler: Arc, + validator_accessor: Arc, + extrinsics_factory: Arc, + ) -> Self { + Self { + top_pool_author, + stf_executor, + state_handler, + validator_accessor, + extrinsics_factory, + _phantom: Default::default(), + } + } + + pub fn execute(&self) -> Result<()> { + let max_duration = Duration::from_secs(5); + let latest_parentchain_header = self.get_latest_parentchain_header()?; + + let mut parentchain_effects: Vec = Vec::new(); + + let shards = self.state_handler.list_shards()?; + debug!("Executing calls on {} shard(s)", shards.len()); + + for shard in shards { + let trusted_calls = self.top_pool_author.get_pending_trusted_calls(shard); + debug!("Executing {} trusted calls on shard {:?}", trusted_calls.len(), shard); + + let batch_execution_result = self.stf_executor.propose_state_update( + &trusted_calls, + &latest_parentchain_header, + &shard, + max_duration, + |mut state| { + Stf::reset_events(&mut state); + state + }, + )?; + + parentchain_effects + .append(&mut batch_execution_result.get_extrinsic_callbacks().clone()); + + let failed_operations = batch_execution_result.get_failed_operations(); + let successful_operations: Vec = batch_execution_result + .get_executed_operation_hashes() + .into_iter() + .map(|h| ExecutedOperation::success(h, TrustedOperationOrHash::Hash(h), Vec::new())) + .collect(); + + // Remove all not successfully executed operations from the top pool. + self.remove_calls_from_pool(&shard, failed_operations); + + // Apply the state update + self.apply_state_update(&shard, batch_execution_result.state_after_execution)?; + + // Remove successful operations from pool + self.remove_calls_from_pool(&shard, successful_operations); + + // TODO: notify parentchain about executed operations? -> add to parentchain effects + } + + if !parentchain_effects.is_empty() { + self.send_parentchain_effects(parentchain_effects)?; + } + + Ok(()) + } + + fn get_latest_parentchain_header(&self) -> Result { + let header = self.validator_accessor.execute_on_validator(|v| { + let latest_parentchain_header = v.latest_finalized_header(v.num_relays())?; + Ok(latest_parentchain_header) + })?; + Ok(header) + } + + fn apply_state_update( + &self, + shard: &ShardIdentifier, + updated_state: ::Externalities, + ) -> Result<()> { + self.state_handler.reset(updated_state, shard)?; + Ok(()) + } + + fn send_parentchain_effects(&self, parentchain_effects: Vec) -> Result<()> { + let extrinsics = self + .extrinsics_factory + .create_extrinsics(parentchain_effects.as_slice(), None)?; + self.validator_accessor + .execute_mut_on_validator(|v| v.send_extrinsics(extrinsics))?; + Ok(()) + } + + fn remove_calls_from_pool( + &self, + shard: &ShardIdentifier, + executed_calls: Vec, + ) -> Vec { + let executed_calls_tuple: Vec<_> = executed_calls + .iter() + .map(|e| (e.trusted_operation_or_hash.clone(), e.is_success())) + .collect(); + let failed_to_remove_hashes = + self.top_pool_author.remove_calls_from_pool(*shard, executed_calls_tuple); + + let failed_executed_calls: Vec<_> = executed_calls + .into_iter() + .filter(|e| failed_to_remove_hashes.contains(&e.trusted_operation_or_hash)) + .collect(); + + failed_executed_calls + } +} + +#[cfg(test)] +mod tests { + + use super::*; + use codec::{Decode, Encode}; + use ita_stf::{KeyPair, TrustedCall, TrustedOperation}; + use itc_parentchain_light_client::mocks::validator_access_mock::ValidatorAccessMock; + use itp_extrinsics_factory::mock::ExtrinsicsFactoryMock; + use itp_sgx_externalities::SgxExternalitiesTrait; + use itp_stf_executor::mocks::StfExecutorMock; + use itp_test::mock::handle_state_mock::HandleStateMock; + use itp_top_pool_author::mocks::AuthorApiMock; + use itp_types::Block as ParentchainBlock; + use sp_core::{ed25519, Pair}; + use std::boxed::Box; + + type TestStateHandler = HandleStateMock; + type TestStfInterface = SystemPalletEventInterfaceMock; + type State = ::StateT; + type TestTopPoolAuthor = AuthorApiMock; + type TestStfExecutor = StfExecutorMock; + type TestValidatorAccess = ValidatorAccessMock; + type TestExtrinsicsFactory = ExtrinsicsFactoryMock; + type TestExecutor = Executor< + ParentchainBlock, + TestTopPoolAuthor, + TestStfExecutor, + TestStateHandler, + TestValidatorAccess, + TestExtrinsicsFactory, + TestStfInterface, + >; + + const EVENT_COUNT_KEY: &[u8] = b"event_count"; + + struct SystemPalletEventInterfaceMock; + + impl SystemPalletEventInterface for SystemPalletEventInterfaceMock { + type EventRecord = String; + type EventIndex = u32; + type BlockNumber = u32; + type Hash = String; + + fn get_events(_state: &mut State) -> Vec> { + unimplemented!(); + } + + fn get_event_count(state: &mut State) -> Self::EventIndex { + let encoded_value = state.get(EVENT_COUNT_KEY).unwrap(); + Self::EventIndex::decode(&mut encoded_value.as_slice()).unwrap() + } + + fn get_event_topics( + _state: &mut State, + _topic: &Self::Hash, + ) -> Vec<(Self::BlockNumber, Self::EventIndex)> { + unimplemented!() + } + + fn reset_events(state: &mut State) { + state.insert(EVENT_COUNT_KEY.to_vec(), 0u32.encode()); + } + } + + #[test] + fn executing_tops_from_pool_works() { + let stf_executor = Arc::new(TestStfExecutor::new(State::default())); + let top_pool_author = Arc::new(TestTopPoolAuthor::default()); + top_pool_author.submit_top(create_trusted_operation().encode(), shard()); + + assert_eq!(1, top_pool_author.pending_tops(shard()).unwrap().len()); + + let executor = create_executor(top_pool_author.clone(), stf_executor); + executor.execute().unwrap(); + + assert!(top_pool_author.pending_tops(shard()).unwrap().is_empty()); + } + + #[test] + fn reset_events_is_called() { + let mut state = State::default(); + let event_count = 5; + state.insert(EVENT_COUNT_KEY.to_vec(), event_count.encode()); + + let stf_executor = Arc::new(TestStfExecutor::new(state)); + assert_eq!(TestStfInterface::get_event_count(&mut stf_executor.get_state()), event_count); + + let top_pool_author = Arc::new(TestTopPoolAuthor::default()); + + let executor = create_executor(top_pool_author, stf_executor.clone()); + + executor.execute().unwrap(); + + assert_eq!(TestStfInterface::get_event_count(&mut stf_executor.get_state()), 0); + } + + fn create_executor( + top_pool_author: Arc, + stf_executor: Arc, + ) -> TestExecutor { + let state_handler = Arc::new(TestStateHandler::from_shard(shard()).unwrap()); + let validator_access = Arc::new(TestValidatorAccess::default()); + let extrinsics_factory = Arc::new(TestExtrinsicsFactory::default()); + + TestExecutor::new( + top_pool_author, + stf_executor, + state_handler, + validator_access, + extrinsics_factory, + ) + } + + fn create_trusted_operation() -> TrustedOperation { + let sender = ed25519::Pair::from_seed(b"33345678901234567890123456789012"); + let receiver = ed25519::Pair::from_seed(b"14565678901234567890123456789012"); + + let trusted_call = TrustedCall::balance_transfer( + sender.public().into(), + receiver.public().into(), + 10000u128, + ); + let call_signed = + trusted_call.sign(&KeyPair::Ed25519(Box::new(sender)), 0, &mr_enclave(), &shard()); + TrustedOperation::indirect_call(call_signed) + } + + fn mr_enclave() -> [u8; 32] { + [4u8; 32] + } + + fn shard() -> ShardIdentifier { + ShardIdentifier::default() + } +} diff --git a/tee-worker/core/offchain-worker-executor/src/lib.rs b/tee-worker/core/offchain-worker-executor/src/lib.rs new file mode 100644 index 0000000000..d30a11ba0b --- /dev/null +++ b/tee-worker/core/offchain-worker-executor/src/lib.rs @@ -0,0 +1,33 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +#![cfg_attr(not(feature = "std"), no_std)] + +#[cfg(all(feature = "std", feature = "sgx"))] +compile_error!("feature \"std\" and feature \"sgx\" cannot be enabled at the same time"); + +#[cfg(all(not(feature = "std"), feature = "sgx"))] +extern crate sgx_tstd as std; + +// re-export module to properly feature gate sgx and regular std environment +#[cfg(all(not(feature = "std"), feature = "sgx"))] +pub mod sgx_reexport_prelude { + pub use thiserror_sgx as thiserror; +} + +pub mod error; +pub mod executor; diff --git a/tee-worker/core/parentchain/block-import-dispatcher/Cargo.toml b/tee-worker/core/parentchain/block-import-dispatcher/Cargo.toml new file mode 100644 index 0000000000..e2d2550189 --- /dev/null +++ b/tee-worker/core/parentchain/block-import-dispatcher/Cargo.toml @@ -0,0 +1,53 @@ +[package] +authors = ["Integritee AG "] +edition = "2021" +name = "itc-parentchain-block-import-dispatcher" +version = "0.9.0" + +[dependencies] +# sgx dependencies +sgx_tstd = { branch = "master", git = "https://github.com/apache/teaclave-sgx-sdk.git", optional = true } +sgx_types = { branch = "master", git = "https://github.com/apache/teaclave-sgx-sdk.git" } + +# local dependencies +itc-parentchain-block-importer = { path = "../block-importer", default-features = false } +itp-block-import-queue = { path = "../../../core-primitives/block-import-queue", default-features = false } + +# sgx enabled external libraries +thiserror_sgx = { package = "thiserror", git = "https://github.com/mesalock-linux/thiserror-sgx", tag = "sgx_1.1.3", optional = true } + +# crates.io std-only compatible libraries (make sure these versions match with the sgx-enabled ones above) +thiserror = { version = "1.0", optional = true } + +# crates.io no-std compatible libraries +log = { version = "0.4", default-features = false } +sp-runtime = { default-features = false, git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.29" } + +[dev-dependencies] +itc-parentchain-block-importer = { path = "../block-importer", features = ["mocks"] } +itp-types = { path = "../../../core-primitives/types" } + +[features] +default = ["std"] +sgx = [ + # sgx + "sgx_tstd", + # local + "itc-parentchain-block-importer/sgx", + "itp-block-import-queue/sgx", + # sgx enabled external libraries + "thiserror_sgx", +] +std = [ + # local + "itc-parentchain-block-importer/std", + "itp-block-import-queue/std", + # no-std compatible libraries + "log/std", + "sp-runtime/std", + # std-only compatible libraries + "thiserror", +] + +# feature to export mock implementations, only to be used for dev-dependencies! +mocks = [] diff --git a/tee-worker/core/parentchain/block-import-dispatcher/src/error.rs b/tee-worker/core/parentchain/block-import-dispatcher/src/error.rs new file mode 100644 index 0000000000..bc75fd97c2 --- /dev/null +++ b/tee-worker/core/parentchain/block-import-dispatcher/src/error.rs @@ -0,0 +1,47 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +#[cfg(all(not(feature = "std"), feature = "sgx"))] +use crate::sgx_reexport_prelude::*; + +use sgx_types::sgx_status_t; +use std::boxed::Box; + +pub type Result = core::result::Result; + +/// Parentchain block importer error. +#[derive(Debug, thiserror::Error)] +pub enum Error { + #[error("SGX error, status: {0}")] + Sgx(sgx_status_t), + #[error("Two Dispatcher types assigned. Please double check the initialization process.")] + CanNotAssignTwoDispatcher, + #[error("Even though there is no dispatcher assigned, the dispatch function is called.")] + NoDispatcherAssigned, + #[error("Block import queue error: {0}")] + BlockImportQueue(#[from] itp_block_import_queue::error::Error), + #[error("Block import error: {0}")] + BlockImport(#[from] itc_parentchain_block_importer::error::Error), + #[error(transparent)] + Other(#[from] Box), +} + +impl From for Error { + fn from(sgx_status: sgx_status_t) -> Self { + Self::Sgx(sgx_status) + } +} diff --git a/tee-worker/core/parentchain/block-import-dispatcher/src/immediate_dispatcher.rs b/tee-worker/core/parentchain/block-import-dispatcher/src/immediate_dispatcher.rs new file mode 100644 index 0000000000..c639ddeafa --- /dev/null +++ b/tee-worker/core/parentchain/block-import-dispatcher/src/immediate_dispatcher.rs @@ -0,0 +1,100 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +use crate::{error::Result, DispatchBlockImport}; +use itc_parentchain_block_importer::ImportParentchainBlocks; +use log::*; +use std::{boxed::Box, vec::Vec}; + +/// Block import dispatcher that immediately imports the blocks, without any processing or queueing. +pub struct ImmediateDispatcher { + block_importer: BlockImporter, + import_event_observers: Vec>, +} + +impl ImmediateDispatcher { + pub fn new(block_importer: BlockImporter) -> Self { + ImmediateDispatcher { block_importer, import_event_observers: Vec::new() } + } + + pub fn with_observer(self, callback: F) -> Self + where + F: Fn() + Send + Sync + 'static, + { + let mut updated_observers = self.import_event_observers; + updated_observers.push(Box::new(callback)); + + Self { block_importer: self.block_importer, import_event_observers: updated_observers } + } +} + +impl DispatchBlockImport + for ImmediateDispatcher +where + BlockImporter: ImportParentchainBlocks, +{ + fn dispatch_import(&self, blocks: Vec) -> Result<()> { + debug!("Importing {} parentchain blocks", blocks.len()); + self.block_importer.import_parentchain_blocks(blocks)?; + debug!("Notifying {} observers of import", self.import_event_observers.len()); + self.import_event_observers.iter().for_each(|callback| callback()); + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use itc_parentchain_block_importer::block_importer_mock::ParentchainBlockImporterMock; + use std::{ + sync::{Arc, RwLock}, + vec, + }; + + type SignedBlockType = u32; + type TestBlockImporter = ParentchainBlockImporterMock; + type TestDispatcher = ImmediateDispatcher; + + #[derive(Default)] + struct NotificationCounter { + counter: RwLock, + } + + impl NotificationCounter { + fn increment(&self) { + *self.counter.write().unwrap() += 1; + } + + pub fn get_counter(&self) -> usize { + *self.counter.read().unwrap() + } + } + + #[test] + fn listeners_get_notified_upon_import() { + let block_importer = TestBlockImporter::default(); + let notification_counter = Arc::new(NotificationCounter::default()); + let counter_clone = notification_counter.clone(); + let dispatcher = TestDispatcher::new(block_importer).with_observer(move || { + counter_clone.increment(); + }); + + dispatcher.dispatch_import(vec![1u32, 2u32]).unwrap(); + + assert_eq!(1, notification_counter.get_counter()); + } +} diff --git a/tee-worker/core/parentchain/block-import-dispatcher/src/lib.rs b/tee-worker/core/parentchain/block-import-dispatcher/src/lib.rs new file mode 100644 index 0000000000..758c456a95 --- /dev/null +++ b/tee-worker/core/parentchain/block-import-dispatcher/src/lib.rs @@ -0,0 +1,111 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ +//! Dispatching of block imports. + +#![cfg_attr(not(feature = "std"), no_std)] + +#[cfg(all(feature = "std", feature = "sgx"))] +compile_error!("feature \"std\" and feature \"sgx\" cannot be enabled at the same time"); + +#[cfg(all(not(feature = "std"), feature = "sgx"))] +extern crate sgx_tstd as std; + +// re-export module to properly feature gate sgx and regular std environment +#[cfg(all(not(feature = "std"), feature = "sgx"))] +pub mod sgx_reexport_prelude { + pub use thiserror_sgx as thiserror; +} + +pub mod error; +pub mod immediate_dispatcher; +pub mod triggered_dispatcher; + +#[cfg(feature = "mocks")] +pub mod trigger_parentchain_block_import_mock; + +use crate::triggered_dispatcher::TriggerParentchainBlockImport; +use error::{Error, Result}; +use std::{sync::Arc, vec::Vec}; + +/// Trait to dispatch blocks for import into the local light-client. +pub trait DispatchBlockImport { + /// Dispatch blocks to be imported. + /// + /// The blocks may be imported immediately, get queued, delayed or grouped. + fn dispatch_import(&self, blocks: Vec) -> Result<()>; +} + +/// Wrapper for the actual dispatchers. Allows to define one global type for +/// both dispatchers without changing the global variable when switching +/// the dispatcher type. It also allows for empty dispatchers, for use cases that +/// do not need block syncing for a specific parentchain type. +pub enum BlockImportDispatcher { + TriggeredDispatcher(Arc), + ImmediateDispatcher(Arc), + EmptyDispatcher, +} + +impl + BlockImportDispatcher +where + TriggeredDispatcher: TriggerParentchainBlockImport, +{ + pub fn new_triggered_dispatcher(triggered_dispatcher: Arc) -> Self { + BlockImportDispatcher::TriggeredDispatcher(triggered_dispatcher) + } + + pub fn new_immediate_dispatcher(immediate_dispatcher: Arc) -> Self { + BlockImportDispatcher::ImmediateDispatcher(immediate_dispatcher) + } + + pub fn new_empty_dispatcher() -> Self { + BlockImportDispatcher::EmptyDispatcher + } + + pub fn triggered_dispatcher(&self) -> Option> { + match self { + BlockImportDispatcher::TriggeredDispatcher(triggered_dispatcher) => + Some(triggered_dispatcher.clone()), + _ => None, + } + } + + pub fn immediate_dispatcher(&self) -> Option> { + match self { + BlockImportDispatcher::ImmediateDispatcher(immediate_dispatcher) => + Some(immediate_dispatcher.clone()), + _ => None, + } + } +} + +impl DispatchBlockImport + for BlockImportDispatcher +where + TriggeredDispatcher: DispatchBlockImport, + ImmediateDispatcher: DispatchBlockImport, +{ + fn dispatch_import(&self, blocks: Vec) -> Result<()> { + match self { + BlockImportDispatcher::TriggeredDispatcher(dispatcher) => + dispatcher.dispatch_import(blocks), + BlockImportDispatcher::ImmediateDispatcher(dispatcher) => + dispatcher.dispatch_import(blocks), + BlockImportDispatcher::EmptyDispatcher => Err(Error::NoDispatcherAssigned), + } + } +} diff --git a/tee-worker/core/parentchain/block-import-dispatcher/src/trigger_parentchain_block_import_mock.rs b/tee-worker/core/parentchain/block-import-dispatcher/src/trigger_parentchain_block_import_mock.rs new file mode 100644 index 0000000000..a4953a4fbb --- /dev/null +++ b/tee-worker/core/parentchain/block-import-dispatcher/src/trigger_parentchain_block_import_mock.rs @@ -0,0 +1,102 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +#[cfg(feature = "sgx")] +use std::sync::SgxRwLock as RwLock; + +#[cfg(feature = "std")] +use std::sync::RwLock; + +use crate::{error::Result, triggered_dispatcher::TriggerParentchainBlockImport}; + +/// Mock for `TriggerParentchainBlockImport`, to be used in unit tests. +/// +/// Allows setting the latest imported block, which is returned upon calling +/// the import methods. +pub struct TriggerParentchainBlockImportMock { + latest_imported: Option, + import_has_been_called: RwLock, +} + +impl TriggerParentchainBlockImportMock { + pub fn with_latest_imported(mut self, maybe_block: Option) -> Self { + self.latest_imported = maybe_block; + self + } + + pub fn has_import_been_called(&self) -> bool { + let import_flag = self.import_has_been_called.read().unwrap(); + *import_flag + } +} + +impl Default for TriggerParentchainBlockImportMock { + fn default() -> Self { + TriggerParentchainBlockImportMock { + latest_imported: None, + import_has_been_called: RwLock::new(false), + } + } +} + +impl TriggerParentchainBlockImport + for TriggerParentchainBlockImportMock +where + SignedBlockType: Clone, +{ + type SignedBlockType = SignedBlockType; + + fn import_all(&self) -> Result> { + let mut import_flag = self.import_has_been_called.write().unwrap(); + *import_flag = true; + Ok(self.latest_imported.clone()) + } + + fn import_all_but_latest(&self) -> Result<()> { + let mut import_flag = self.import_has_been_called.write().unwrap(); + *import_flag = true; + Ok(()) + } + + fn import_until( + &self, + _predicate: impl Fn(&SignedBlockType) -> bool, + ) -> Result> { + let mut import_flag = self.import_has_been_called.write().unwrap(); + *import_flag = true; + Ok(self.latest_imported.clone()) + } + + fn peek( + &self, + predicate: impl Fn(&SignedBlockType) -> bool, + ) -> Result> { + match &self.latest_imported { + None => Ok(None), + Some(block) => { + if predicate(block) { + return Ok(Some(block.clone())) + } + Ok(None) + }, + } + } + + fn peek_latest(&self) -> Result> { + Ok(self.latest_imported.clone()) + } +} diff --git a/tee-worker/core/parentchain/block-import-dispatcher/src/triggered_dispatcher.rs b/tee-worker/core/parentchain/block-import-dispatcher/src/triggered_dispatcher.rs new file mode 100644 index 0000000000..b5c824fac2 --- /dev/null +++ b/tee-worker/core/parentchain/block-import-dispatcher/src/triggered_dispatcher.rs @@ -0,0 +1,272 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +//! A block import dispatcher that retains all blocks in a queue until import is triggered. + +use crate::{ + error::{Error, Result}, + DispatchBlockImport, +}; +use itc_parentchain_block_importer::ImportParentchainBlocks; +use itp_block_import_queue::{PeekBlockQueue, PopFromBlockQueue, PushToBlockQueue}; +use log::debug; +use std::vec::Vec; + +/// Trait to specifically trigger the import of parentchain blocks. +pub trait TriggerParentchainBlockImport { + type SignedBlockType; + /// Trigger the import of all queued block, **including** the latest one. + /// + /// Returns the latest imported block (if any). + fn import_all(&self) -> Result>; + + /// Trigger import of all queued blocks, **except** the latest one. + fn import_all_but_latest(&self) -> Result<()>; + + /// Trigger import of all blocks up to **and including** a specific block. + /// + /// If no block in the queue matches, then no blocks will be imported. + /// Returns the latest imported block (if any). + fn import_until( + &self, + predicate: impl Fn(&Self::SignedBlockType) -> bool, + ) -> Result>; + + /// Search the import queue with a given predicate and return a reference + /// to the first element that matches the predicate. + fn peek( + &self, + predicate: impl Fn(&Self::SignedBlockType) -> bool, + ) -> Result>; + + /// Peek the latest block in the import queue. Returns None if queue is empty. + fn peek_latest(&self) -> Result>; +} + +/// Dispatcher for block imports that retains blocks until the import is triggered, using the +/// `TriggerParentchainBlockImport` trait implementation. +pub struct TriggeredDispatcher { + block_importer: BlockImporter, + import_queue: BlockImportQueue, +} + +impl TriggeredDispatcher +where + BlockImporter: ImportParentchainBlocks, + BlockImportQueue: PushToBlockQueue + + PopFromBlockQueue, +{ + pub fn new(block_importer: BlockImporter, block_import_queue: BlockImportQueue) -> Self { + TriggeredDispatcher { block_importer, import_queue: block_import_queue } + } +} + +impl DispatchBlockImport + for TriggeredDispatcher +where + BlockImporter: ImportParentchainBlocks, + BlockImportQueue: + PushToBlockQueue + PopFromBlockQueue, +{ + fn dispatch_import(&self, blocks: Vec) -> Result<()> { + debug!("Pushing parentchain block(s) ({}) to import queue", blocks.len()); + // Push all the blocks to be dispatched into the queue. + self.import_queue.push_multiple(blocks).map_err(Error::BlockImportQueue) + } +} + +impl TriggerParentchainBlockImport + for TriggeredDispatcher +where + BlockImporter: ImportParentchainBlocks, + BlockImportQueue: PushToBlockQueue + + PopFromBlockQueue + + PeekBlockQueue, +{ + type SignedBlockType = BlockImporter::SignedBlockType; + + fn import_all(&self) -> Result> { + let blocks_to_import = self.import_queue.pop_all().map_err(Error::BlockImportQueue)?; + + let latest_imported_block = blocks_to_import.last().map(|b| (*b).clone()); + + debug!("Trigger import of all parentchain blocks in queue ({})", blocks_to_import.len()); + + self.block_importer + .import_parentchain_blocks(blocks_to_import) + .map_err(Error::BlockImport)?; + + Ok(latest_imported_block) + } + + fn import_all_but_latest(&self) -> Result<()> { + let blocks_to_import = + self.import_queue.pop_all_but_last().map_err(Error::BlockImportQueue)?; + + debug!( + "Trigger import of all parentchain blocks, except the latest, from queue ({})", + blocks_to_import.len() + ); + + self.block_importer + .import_parentchain_blocks(blocks_to_import) + .map_err(Error::BlockImport) + } + + fn import_until( + &self, + predicate: impl Fn(&BlockImporter::SignedBlockType) -> bool, + ) -> Result> { + let blocks_to_import = + self.import_queue.pop_until(predicate).map_err(Error::BlockImportQueue)?; + + let latest_imported_block = blocks_to_import.last().map(|b| (*b).clone()); + + debug!( + "Import of parentchain blocks has been triggered, importing {} blocks from queue", + blocks_to_import.len() + ); + + self.block_importer + .import_parentchain_blocks(blocks_to_import) + .map_err(Error::BlockImport)?; + + Ok(latest_imported_block) + } + + fn peek( + &self, + predicate: impl Fn(&BlockImporter::SignedBlockType) -> bool, + ) -> Result> { + debug!( + "Peek find parentchain import queue (currently has {} elements)", + self.import_queue.peek_queue_size().unwrap_or(0) + ); + self.import_queue.peek_find(predicate).map_err(Error::BlockImportQueue) + } + + fn peek_latest(&self) -> Result> { + debug!( + "Peek latest parentchain import queue (currently has {} elements)", + self.import_queue.peek_queue_size().unwrap_or(0) + ); + self.import_queue.peek_last().map_err(Error::BlockImportQueue) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use itc_parentchain_block_importer::block_importer_mock::ParentchainBlockImporterMock; + use itp_block_import_queue::{BlockImportQueue, PopFromBlockQueue}; + + type SignedBlockType = u32; + type TestBlockImporter = ParentchainBlockImporterMock; + type TestQueue = BlockImportQueue; + type TestDispatcher = TriggeredDispatcher; + + #[test] + fn dispatching_blocks_imports_none_if_not_triggered() { + let dispatcher = test_fixtures(); + + dispatcher.dispatch_import(vec![1, 2, 3, 4, 5]).unwrap(); + + assert!(dispatcher.block_importer.get_all_imported_blocks().is_empty()); + assert_eq!(dispatcher.import_queue.pop_all().unwrap(), vec![1, 2, 3, 4, 5]); + } + + #[test] + fn dispatching_blocks_multiple_times_add_all_to_queue() { + let dispatcher = test_fixtures(); + + dispatcher.dispatch_import(vec![1, 2, 3, 4, 5]).unwrap(); + dispatcher.dispatch_import(vec![6, 7, 8]).unwrap(); + + assert!(dispatcher.block_importer.get_all_imported_blocks().is_empty()); + assert_eq!(dispatcher.import_queue.pop_all().unwrap(), vec![1, 2, 3, 4, 5, 6, 7, 8]); + } + + #[test] + fn triggering_import_all_empties_queue() { + let dispatcher = test_fixtures(); + + dispatcher.dispatch_import(vec![1, 2, 3, 4, 5]).unwrap(); + let latest_imported = dispatcher.import_all().unwrap().unwrap(); + + assert_eq!(latest_imported, 5); + assert_eq!(dispatcher.block_importer.get_all_imported_blocks(), vec![1, 2, 3, 4, 5]); + assert!(dispatcher.import_queue.is_empty().unwrap()); + } + + #[test] + fn triggering_import_all_on_empty_queue_imports_none() { + let dispatcher = test_fixtures(); + + dispatcher.dispatch_import(vec![]).unwrap(); + let maybe_latest_imported = dispatcher.import_all().unwrap(); + + assert!(maybe_latest_imported.is_none()); + assert_eq!( + dispatcher.block_importer.get_all_imported_blocks(), + Vec::::default() + ); + assert!(dispatcher.import_queue.is_empty().unwrap()); + } + + #[test] + fn triggering_import_until_leaves_remaining_in_queue() { + let dispatcher = test_fixtures(); + + dispatcher.dispatch_import(vec![1, 2, 3, 4, 5]).unwrap(); + let latest_imported = + dispatcher.import_until(|i: &SignedBlockType| i == &4).unwrap().unwrap(); + + assert_eq!(latest_imported, 4); + assert_eq!(dispatcher.block_importer.get_all_imported_blocks(), vec![1, 2, 3, 4]); + assert_eq!(dispatcher.import_queue.pop_all().unwrap(), vec![5]); + } + + #[test] + fn triggering_import_until_with_no_match_imports_nothing() { + let dispatcher = test_fixtures(); + + dispatcher.dispatch_import(vec![1, 2, 3, 4, 5]).unwrap(); + let maybe_latest_imported = dispatcher.import_until(|i: &SignedBlockType| i == &8).unwrap(); + + assert!(maybe_latest_imported.is_none()); + assert!(dispatcher.block_importer.get_all_imported_blocks().is_empty()); + assert_eq!(dispatcher.import_queue.pop_all().unwrap(), vec![1, 2, 3, 4, 5]); + } + + #[test] + fn trigger_import_all_but_latest_works() { + let dispatcher = test_fixtures(); + + dispatcher.dispatch_import(vec![1, 2, 3, 4, 5]).unwrap(); + dispatcher.import_all_but_latest().unwrap(); + + assert_eq!(dispatcher.block_importer.get_all_imported_blocks(), vec![1, 2, 3, 4]); + assert_eq!(dispatcher.import_queue.pop_all().unwrap(), vec![5]); + } + + fn test_fixtures() -> TestDispatcher { + let import_queue = BlockImportQueue::::default(); + let block_importer = ParentchainBlockImporterMock::::default(); + let dispatcher = TriggeredDispatcher::new(block_importer, import_queue); + dispatcher + } +} diff --git a/tee-worker/core/parentchain/block-importer/Cargo.toml b/tee-worker/core/parentchain/block-importer/Cargo.toml new file mode 100644 index 0000000000..5dd7c79536 --- /dev/null +++ b/tee-worker/core/parentchain/block-importer/Cargo.toml @@ -0,0 +1,63 @@ +[package] +authors = ["Integritee AG "] +edition = "2021" +name = "itc-parentchain-block-importer" +version = "0.9.0" + +[dependencies] +# sgx dependencies +sgx_tstd = { branch = "master", git = "https://github.com/apache/teaclave-sgx-sdk.git", optional = true } +sgx_types = { branch = "master", git = "https://github.com/apache/teaclave-sgx-sdk.git" } + +# local dependencies +ita-stf = { path = "../../../app-libs/stf", default-features = false } +itc-parentchain-indirect-calls-executor = { path = "../indirect-calls-executor", default-features = false } +itc-parentchain-light-client = { path = "../light-client", default-features = false } +itp-extrinsics-factory = { path = "../../../core-primitives/extrinsics-factory", default-features = false } +itp-settings = { path = "../../../core-primitives/settings" } +itp-stf-executor = { path = "../../../core-primitives/stf-executor", default-features = false } +itp-types = { path = "../../../core-primitives/types", default-features = false } + +# sgx enabled external libraries +thiserror_sgx = { package = "thiserror", git = "https://github.com/mesalock-linux/thiserror-sgx", tag = "sgx_1.1.3", optional = true } + +# crates.io std-only compatible external libraries (make sure these versions match with the sgx-enabled ones above) +thiserror = { version = "1.0", optional = true } + +# crates.io no-std compatible libraries +codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } +log = { version = "0.4", default-features = false } +sp-runtime = { default-features = false, git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.29" } + +[features] +default = ["std"] +sgx = [ + # sgx + "sgx_tstd", + # local + "ita-stf/sgx", + "itc-parentchain-indirect-calls-executor/sgx", + "itc-parentchain-light-client/sgx", + "itp-extrinsics-factory/sgx", + "itp-stf-executor/sgx", + # sgx enabled external libraries + "thiserror_sgx", +] +std = [ + # local + "ita-stf/std", + "itc-parentchain-indirect-calls-executor/std", + "itc-parentchain-light-client/std", + "itp-extrinsics-factory/std", + "itp-stf-executor/std", + "itp-types/std", + # no-std compatible libraries + "codec/std", + "log/std", + "sp-runtime/std", + # std compatible external + "thiserror", +] + +# feature to export mock implementations, only to be used for dev-dependencies! +mocks = [] diff --git a/tee-worker/core/parentchain/block-importer/src/block_importer.rs b/tee-worker/core/parentchain/block-importer/src/block_importer.rs new file mode 100644 index 0000000000..a5bc1976c3 --- /dev/null +++ b/tee-worker/core/parentchain/block-importer/src/block_importer.rs @@ -0,0 +1,171 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +//! Imports parentchain blocks and executes any indirect calls found in the extrinsics. + +use crate::{error::Result, ImportParentchainBlocks}; +use ita_stf::ParentchainHeader; +use itc_parentchain_indirect_calls_executor::ExecuteIndirectCalls; +use itc_parentchain_light_client::{ + concurrent_access::ValidatorAccess, BlockNumberOps, ExtrinsicSender, LightClientState, + Validator, +}; +use itp_extrinsics_factory::CreateExtrinsics; +use itp_stf_executor::traits::StfUpdateState; +use itp_types::{OpaqueCall, H256}; +use log::*; +use sp_runtime::{ + generic::SignedBlock as SignedBlockG, + traits::{Block as ParentchainBlockTrait, NumberFor}, +}; +use std::{marker::PhantomData, sync::Arc, vec::Vec}; + +/// Parentchain block import implementation. +pub struct ParentchainBlockImporter< + ParentchainBlock, + ValidatorAccessor, + StfExecutor, + ExtrinsicsFactory, + IndirectCallsExecutor, +> where + ParentchainBlock: ParentchainBlockTrait, + NumberFor: BlockNumberOps, + ValidatorAccessor: ValidatorAccess, + StfExecutor: StfUpdateState, + ExtrinsicsFactory: CreateExtrinsics, + IndirectCallsExecutor: ExecuteIndirectCalls, +{ + validator_accessor: Arc, + stf_executor: Arc, + extrinsics_factory: Arc, + indirect_calls_executor: Arc, + _phantom: PhantomData, +} + +impl< + ParentchainBlock, + ValidatorAccessor, + StfExecutor, + ExtrinsicsFactory, + IndirectCallsExecutor, + > + ParentchainBlockImporter< + ParentchainBlock, + ValidatorAccessor, + StfExecutor, + ExtrinsicsFactory, + IndirectCallsExecutor, + > where + ParentchainBlock: ParentchainBlockTrait, + NumberFor: BlockNumberOps, + ValidatorAccessor: ValidatorAccess, + StfExecutor: StfUpdateState, + ExtrinsicsFactory: CreateExtrinsics, + IndirectCallsExecutor: ExecuteIndirectCalls, +{ + pub fn new( + validator_accessor: Arc, + stf_executor: Arc, + extrinsics_factory: Arc, + indirect_calls_executor: Arc, + ) -> Self { + ParentchainBlockImporter { + validator_accessor, + stf_executor, + extrinsics_factory, + indirect_calls_executor, + _phantom: Default::default(), + } + } +} + +impl< + ParentchainBlock, + ValidatorAccessor, + StfExecutor, + ExtrinsicsFactory, + IndirectCallsExecutor, + > ImportParentchainBlocks + for ParentchainBlockImporter< + ParentchainBlock, + ValidatorAccessor, + StfExecutor, + ExtrinsicsFactory, + IndirectCallsExecutor, + > where + ParentchainBlock: ParentchainBlockTrait, + NumberFor: BlockNumberOps, + ValidatorAccessor: ValidatorAccess, + StfExecutor: StfUpdateState, + ExtrinsicsFactory: CreateExtrinsics, + IndirectCallsExecutor: ExecuteIndirectCalls, +{ + type SignedBlockType = SignedBlockG; + + fn import_parentchain_blocks( + &self, + blocks_to_import: Vec, + ) -> Result<()> { + let mut calls = Vec::::new(); + + debug!("Import blocks to light-client!"); + for signed_block in blocks_to_import.into_iter() { + // Check if there are any extrinsics in the to-be-imported block that we sent and cached in the light-client before. + // If so, remove them now from the cache. + if let Err(e) = self.validator_accessor.execute_mut_on_validator(|v| { + v.check_xt_inclusion(v.num_relays(), &signed_block.block)?; + + v.submit_block(v.num_relays(), &signed_block) + }) { + error!("[Validator] Header submission failed: {:?}", e); + return Err(e.into()) + } + + let block = signed_block.block; + // Perform state updates. + if let Err(e) = self.stf_executor.update_states(block.header()) { + error!("Error performing state updates upon block import"); + return Err(e.into()) + } + + // Execute indirect calls that were found in the extrinsics of the block, + // incl. shielding and unshielding. + match self.indirect_calls_executor.execute_indirect_calls_in_extrinsics(&block) { + Ok(executed_shielding_calls) => { + calls.push(executed_shielding_calls); + }, + Err(_) => error!("Error executing relevant extrinsics"), + }; + + info!( + "Successfully imported parentchain block (number: {}, hash: {})", + block.header().number, + block.header().hash() + ); + } + + // Create extrinsics for all `unshielding` and `block processed` calls we've gathered. + let parentchain_extrinsics = + self.extrinsics_factory.create_extrinsics(calls.as_slice(), None)?; + + // Sending the extrinsic requires mut access because the validator caches the sent extrinsics internally. + self.validator_accessor + .execute_mut_on_validator(|v| v.send_extrinsics(parentchain_extrinsics))?; + + Ok(()) + } +} diff --git a/tee-worker/core/parentchain/block-importer/src/block_importer_mock.rs b/tee-worker/core/parentchain/block-importer/src/block_importer_mock.rs new file mode 100644 index 0000000000..912c3409b4 --- /dev/null +++ b/tee-worker/core/parentchain/block-importer/src/block_importer_mock.rs @@ -0,0 +1,60 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +//! Block importer mock. + +use crate::{ + error::{Error, Result}, + ImportParentchainBlocks, +}; +use std::{sync::RwLock, vec::Vec}; + +/// Mock implementation for the block importer. +/// +/// Just stores all the blocks that were sent to import internally. +#[derive(Default)] +pub struct ParentchainBlockImporterMock { + imported_blocks: RwLock>, +} + +impl ParentchainBlockImporterMock +where + SignedBlockT: Clone, +{ + pub fn get_all_imported_blocks(&self) -> Vec { + let imported_blocks_lock = self.imported_blocks.read().unwrap(); + (*imported_blocks_lock).clone() + } +} + +impl ImportParentchainBlocks for ParentchainBlockImporterMock +where + SignedBlockT: Clone, +{ + type SignedBlockType = SignedBlockT; + + fn import_parentchain_blocks( + &self, + blocks_to_import: Vec, + ) -> Result<()> { + let mut imported_blocks_lock = self.imported_blocks.write().map_err(|e| { + Error::Other(format!("failed to acquire lock for imported blocks vec: {:?}", e).into()) + })?; + imported_blocks_lock.extend(blocks_to_import); + Ok(()) + } +} diff --git a/tee-worker/core/parentchain/block-importer/src/error.rs b/tee-worker/core/parentchain/block-importer/src/error.rs new file mode 100644 index 0000000000..856aa84ef2 --- /dev/null +++ b/tee-worker/core/parentchain/block-importer/src/error.rs @@ -0,0 +1,51 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +#[cfg(all(not(feature = "std"), feature = "sgx"))] +use crate::sgx_reexport_prelude::*; + +use sgx_types::sgx_status_t; +use std::{boxed::Box, format}; + +pub type Result = core::result::Result; + +/// Parentchain block importer error. +#[derive(Debug, thiserror::Error)] +pub enum Error { + #[error("SGX error, status: {0}")] + Sgx(sgx_status_t), + #[error("Extrinsics factory error: {0}")] + ExtrinsicsFactory(#[from] itp_extrinsics_factory::error::Error), + #[error("STF execution error: {0}")] + StfExecution(#[from] itp_stf_executor::error::Error), + #[error("Light-client error: {0}")] + LightClient(#[from] itc_parentchain_light_client::error::Error), + #[error(transparent)] + Other(#[from] Box), +} + +impl From for Error { + fn from(sgx_status: sgx_status_t) -> Self { + Self::Sgx(sgx_status) + } +} + +impl From for Error { + fn from(e: codec::Error) -> Self { + Self::Other(format!("{:?}", e).into()) + } +} diff --git a/tee-worker/core/parentchain/block-importer/src/lib.rs b/tee-worker/core/parentchain/block-importer/src/lib.rs new file mode 100644 index 0000000000..f22b60546f --- /dev/null +++ b/tee-worker/core/parentchain/block-importer/src/lib.rs @@ -0,0 +1,55 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ +//! Parentchain block importing logic. +#![feature(trait_alias)] +#![cfg_attr(not(feature = "std"), no_std)] + +#[cfg(all(feature = "std", feature = "sgx"))] +compile_error!("feature \"std\" and feature \"sgx\" cannot be enabled at the same time"); + +#[cfg(all(not(feature = "std"), feature = "sgx"))] +extern crate sgx_tstd as std; + +// re-export module to properly feature gate sgx and regular std environment +#[cfg(all(not(feature = "std"), feature = "sgx"))] +pub mod sgx_reexport_prelude { + pub use thiserror_sgx as thiserror; +} + +pub mod block_importer; +pub mod error; + +#[cfg(feature = "mocks")] +pub mod block_importer_mock; + +pub use block_importer::*; + +use error::Result; +use std::vec::Vec; + +/// Block import from the parentchain. +pub trait ImportParentchainBlocks { + type SignedBlockType: Clone; + + /// Import parentchain blocks to the light-client (validator): + /// * Scans the blocks for relevant extrinsics + /// * Validates and execute those extrinsics, mutating state + /// * Includes block headers into the light client + /// * Sends `PROCESSED_PARENTCHAIN_BLOCK` extrinsics that include the merkle root of all processed calls + fn import_parentchain_blocks(&self, blocks_to_import: Vec) + -> Result<()>; +} diff --git a/tee-worker/core/parentchain/indirect-calls-executor/Cargo.toml b/tee-worker/core/parentchain/indirect-calls-executor/Cargo.toml new file mode 100644 index 0000000000..542c00d420 --- /dev/null +++ b/tee-worker/core/parentchain/indirect-calls-executor/Cargo.toml @@ -0,0 +1,89 @@ +[package] +authors = ["Integritee AG "] +edition = "2021" +name = "itc-parentchain-indirect-calls-executor" +version = "0.9.0" + +[dependencies] +# sgx dependencies +sgx_tstd = { branch = "master", git = "https://github.com/apache/teaclave-sgx-sdk.git", optional = true } +sgx_types = { branch = "master", git = "https://github.com/apache/teaclave-sgx-sdk.git" } + +# local dependencies +ita-stf = { path = "../../../app-libs/stf", default-features = false } +itp-node-api = { path = "../../../core-primitives/node-api", default-features = false } +itp-ocall-api = { path = "../../../core-primitives/ocall-api", default-features = false } +itp-sgx-crypto = { path = "../../../core-primitives/sgx/crypto", default-features = false } +itp-stf-executor = { path = "../../../core-primitives/stf-executor", default-features = false } +itp-top-pool-author = { path = "../../../core-primitives/top-pool-author", default-features = false } +itp-types = { path = "../../../core-primitives/types", default-features = false } + +# sgx enabled external libraries +futures_sgx = { package = "futures", git = "https://github.com/mesalock-linux/futures-rs-sgx", optional = true } +thiserror_sgx = { package = "thiserror", git = "https://github.com/mesalock-linux/thiserror-sgx", tag = "sgx_1.1.3", optional = true } + +# std compatible external libraries (make sure these versions match with the sgx-enabled ones above) +futures = { version = "0.3.8", optional = true } +thiserror = { version = "1.0", optional = true } + +# no-std compatible libraries +bs58 = { version = "0.4.0", default-features = false, features = ["alloc"] } +codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } +log = { version = "0.4", default-features = false } + +# substrate dep +beefy-merkle-tree = { default-features = false, features = ["keccak"], git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.29" } +sp-core = { default-features = false, features = ["full_crypto"], git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.29" } +sp-runtime = { default-features = false, git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.29" } + +# scs/integritee +substrate-api-client = { git = "https://github.com/scs/substrate-api-client.git", branch = "polkadot-v0.9.29", default-features = false } + +# litentry +ita-sgx-runtime = { path = "../../../app-libs/sgx-runtime", default-features = false } +litentry-primitives = { path = "../../../litentry/primitives", default-features = false } +pallet-imp = { package = "pallet-identity-management", git = "https://github.com/litentry/litentry-parachain.git", branch = "tee-dev", default-features = false } + +[dev-dependencies] +env_logger = "0.9.0" +itc-parentchain-test = { path = "../../../core/parentchain/test" } +itp-sgx-crypto = { path = "../../../core-primitives/sgx/crypto", features = ["mocks"] } +itp-stf-executor = { path = "../../../core-primitives/stf-executor", features = ["mocks"] } +itp-test = { path = "../../../core-primitives/test" } +itp-top-pool-author = { path = "../../../core-primitives/top-pool-author", features = ["mocks"] } + +[features] +default = ["std"] +sgx = [ + "sgx_tstd", + "futures_sgx", + "ita-stf/sgx", + "itp-node-api/sgx", + "itp-sgx-crypto/sgx", + "itp-stf-executor/sgx", + "itp-top-pool-author/sgx", + "thiserror_sgx", + "litentry-primitives/sgx", +] +std = [ + "bs58/std", + "codec/std", + "futures", + "ita-stf/std", + "itp-node-api/std", + "itp-ocall-api/std", + "itp-sgx-crypto/std", + "itp-stf-executor/std", + "itp-top-pool-author/std", + "itp-types/std", + "log/std", + #substrate + "beefy-merkle-tree/std", + "sp-core/std", + "sp-runtime/std", + "substrate-api-client/std", + "thiserror", + "litentry-primitives/std", + "ita-sgx-runtime/std", + "pallet-imp/std", +] diff --git a/tee-worker/core/parentchain/indirect-calls-executor/src/error.rs b/tee-worker/core/parentchain/indirect-calls-executor/src/error.rs new file mode 100644 index 0000000000..7dd778f1b9 --- /dev/null +++ b/tee-worker/core/parentchain/indirect-calls-executor/src/error.rs @@ -0,0 +1,76 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +#[cfg(all(not(feature = "std"), feature = "sgx"))] +use crate::sgx_reexport_prelude::*; + +use sgx_types::sgx_status_t; +use sp_runtime::traits::LookupError; +use std::{boxed::Box, format}; + +pub type Result = core::result::Result; + +/// Indirect calls execution error. +#[derive(Debug, thiserror::Error)] +pub enum Error { + #[error("SGX error, status: {0}")] + Sgx(sgx_status_t), + #[error("STF execution error: {0}")] + StfExecution(#[from] itp_stf_executor::error::Error), + #[error("Node Metadata error: {0:?}")] + NodeMetadata(itp_node_api::metadata::Error), + #[error("Node metadata provider error: {0:?}")] + NodeMetadataProvider(#[from] itp_node_api::metadata::provider::Error), + #[error("Crypto error: {0}")] + Crypto(itp_sgx_crypto::Error), + #[error(transparent)] + Other(#[from] Box), + #[error("AccountId lookup error")] + AccountIdLookup, + #[error("convert parent chain block number error")] + ConvertParentchainBlockNumber, +} + +impl From for Error { + fn from(sgx_status: sgx_status_t) -> Self { + Self::Sgx(sgx_status) + } +} + +impl From for Error { + fn from(e: itp_sgx_crypto::Error) -> Self { + Self::Crypto(e) + } +} + +impl From for Error { + fn from(e: codec::Error) -> Self { + Self::Other(format!("{:?}", e).into()) + } +} + +impl From for Error { + fn from(e: itp_node_api::metadata::Error) -> Self { + Self::NodeMetadata(e) + } +} + +impl From for Error { + fn from(_: LookupError) -> Self { + Self::AccountIdLookup + } +} diff --git a/tee-worker/core/parentchain/indirect-calls-executor/src/indirect_calls_executor.rs b/tee-worker/core/parentchain/indirect-calls-executor/src/indirect_calls_executor.rs new file mode 100644 index 0000000000..6fe0e7fdf6 --- /dev/null +++ b/tee-worker/core/parentchain/indirect-calls-executor/src/indirect_calls_executor.rs @@ -0,0 +1,605 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +//! Execute indirect calls, i.e. extrinsics extracted from parentchain blocks + +#[cfg(all(not(feature = "std"), feature = "sgx"))] +use crate::sgx_reexport_prelude::*; + +use crate::error::Result; +use beefy_merkle_tree::{merkle_root, Keccak256}; +use codec::{Decode, Encode}; +use futures::executor; +use ita_sgx_runtime::{pallet_identity_management::MetadataOf, Runtime}; +use ita_stf::{AccountId, TrustedCall, TrustedOperation}; +use itp_node_api::{ + api_client::ParentchainUncheckedExtrinsic, + metadata::{ + pallet_imp::IMPCallIndexes, pallet_teerex::TeerexCallIndexes, provider::AccessNodeMetadata, + }, +}; +use itp_sgx_crypto::{key_repository::AccessKey, ShieldingCryptoDecrypt, ShieldingCryptoEncrypt}; +use itp_stf_executor::traits::StfEnclaveSigning; +use itp_top_pool_author::traits::AuthorApi; +use itp_types::{CallWorkerFn, OpaqueCall, ShardIdentifier, ShieldFundsFn, H256}; +use litentry_primitives::{Identity, UserShieldingKeyType, ValidationData}; +use log::*; +use pallet_imp::{LinkIdentityFn, SetUserShieldingKeyFn, UnlinkIdentityFn, VerifyIdentityFn}; +use sp_core::blake2_256; +use sp_runtime::traits::{AccountIdLookup, Block as ParentchainBlockTrait, Header, StaticLookup}; +use std::{sync::Arc, vec::Vec}; + +/// Trait to execute the indirect calls found in the extrinsics of a block. +pub trait ExecuteIndirectCalls { + /// Scans blocks for extrinsics that ask the enclave to execute some actions. + /// Executes indirect invocation calls, including shielding and unshielding calls. + /// Returns all unshielding call confirmations as opaque calls and the hashes of executed shielding calls. + fn execute_indirect_calls_in_extrinsics( + &self, + block: &ParentchainBlock, + ) -> Result + where + ParentchainBlock: ParentchainBlockTrait; +} + +// Seems macro can't be pre-/suffix'ed, `concat_ident` doesn'tw work either +macro_rules! is_parentchain_function { + ($fn_name:ident, $call_index_name:ident) => { + fn $fn_name(&self, function: &[u8; 2]) -> bool { + self.node_meta_data_provider + .get_from_metadata(|meta_data| { + let call = match meta_data.$call_index_name() { + Ok(c) => c, + Err(e) => { + error!("Failed to get the indexes for the $call_index_name call from the metadata: {:?}", e); + return false + }, + }; + function == &call + }) + .unwrap_or(false) + } + }; +} + +pub struct IndirectCallsExecutor< + ShieldingKeyRepository, + StfEnclaveSigner, + TopPoolAuthor, + NodeMetadataProvider, +> { + shielding_key_repo: Arc, + stf_enclave_signer: Arc, + top_pool_author: Arc, + node_meta_data_provider: Arc, +} + +impl + IndirectCallsExecutor +where + ShieldingKeyRepository: AccessKey, + ::KeyType: ShieldingCryptoDecrypt + + ShieldingCryptoEncrypt, + StfEnclaveSigner: StfEnclaveSigning, + TopPoolAuthor: AuthorApi + Send + Sync + 'static, + NodeMetadataProvider: AccessNodeMetadata, + NodeMetadataProvider::MetadataType: TeerexCallIndexes + IMPCallIndexes, +{ + pub fn new( + shielding_key_repo: Arc, + stf_enclave_signer: Arc, + top_pool_author: Arc, + node_meta_data_provider: Arc, + ) -> Self { + IndirectCallsExecutor { + shielding_key_repo, + stf_enclave_signer, + top_pool_author, + node_meta_data_provider, + } + } + + fn handle_shield_funds_xt( + &self, + xt: ParentchainUncheckedExtrinsic, + ) -> Result<()> { + let (call, account_encrypted, amount, shard) = xt.function; + info!("Found ShieldFunds extrinsic in block: \nCall: {:?} \nAccount Encrypted {:?} \nAmount: {} \nShard: {}", + call, account_encrypted, amount, bs58::encode(shard.encode()).into_string()); + + debug!("decrypt the account id"); + + let shielding_key = self.shielding_key_repo.retrieve_key()?; + let account_vec = shielding_key.decrypt(&account_encrypted)?; + + let account = AccountId::decode(&mut account_vec.as_slice())?; + + let enclave_account_id = self.stf_enclave_signer.get_enclave_account()?; + let trusted_call = TrustedCall::balance_shield(enclave_account_id, account, amount); + let signed_trusted_call = + self.stf_enclave_signer.sign_call_with_self(&trusted_call, &shard)?; + let trusted_operation = TrustedOperation::indirect_call(signed_trusted_call); + + let encrypted_trusted_call = shielding_key.encrypt(&trusted_operation.encode())?; + self.submit_trusted_call(shard, encrypted_trusted_call); + Ok(()) + } + + fn submit_trusted_call(&self, shard: ShardIdentifier, encrypted_trusted_call: Vec) { + let top_submit_future = + async { self.top_pool_author.submit_top(encrypted_trusted_call, shard).await }; + if let Err(e) = executor::block_on(top_submit_future) { + error!("Error adding indirect trusted call to TOP pool: {:?}", e); + } + } + + /// Creates a processed_parentchain_block extrinsic for a given parentchain block hash and the merkle executed extrinsics. + /// + /// Calculates the merkle root of the extrinsics. In case no extrinsics are supplied, the root will be a hash filled with zeros. + fn create_processed_parentchain_block_call( + &self, + block_hash: H256, + extrinsics: Vec, + block_number: <::Header as Header>::Number, + ) -> Result + where + ParentchainBlock: ParentchainBlockTrait, + { + let call = self.node_meta_data_provider.get_from_metadata(|meta_data| { + meta_data.confirm_processed_parentchain_block_call_indexes() + })??; + + let root: H256 = merkle_root::(extrinsics).into(); + Ok(OpaqueCall::from_tuple(&(call, block_hash, block_number, root))) + } + + is_parentchain_function!(is_shield_funds_function, shield_funds_call_indexes); + is_parentchain_function!(is_call_worker_function, call_worker_call_indexes); + + is_parentchain_function!( + is_set_user_shielding_key_function, + set_user_shielding_key_call_indexes + ); + is_parentchain_function!(is_link_identity_funciton, link_identity_call_indexes); + is_parentchain_function!(is_unlink_identity_funciton, unlink_identity_call_indexes); + is_parentchain_function!(is_verify_identity_funciton, verify_identity_call_indexes); +} + +impl + ExecuteIndirectCalls + for IndirectCallsExecutor< + ShieldingKeyRepository, + StfEnclaveSigner, + TopPoolAuthor, + NodeMetadataProvider, + > where + ShieldingKeyRepository: AccessKey, + ::KeyType: ShieldingCryptoDecrypt + + ShieldingCryptoEncrypt, + StfEnclaveSigner: StfEnclaveSigning, + TopPoolAuthor: AuthorApi + Send + Sync + 'static, + NodeMetadataProvider: AccessNodeMetadata, + NodeMetadataProvider::MetadataType: TeerexCallIndexes + IMPCallIndexes, +{ + fn execute_indirect_calls_in_extrinsics( + &self, + block: &ParentchainBlock, + ) -> Result + where + ParentchainBlock: ParentchainBlockTrait, + { + let block_number = *block.header().number(); + let block_hash = block.hash(); + debug!("Scanning block {:?} for relevant xt", block_number); + let mut executed_shielding_calls = Vec::::new(); + for xt_opaque in block.extrinsics().iter() { + let encoded_xt_opaque = xt_opaque.encode(); + + // Found ShieldFunds extrinsic in block. + if let Ok(xt) = ParentchainUncheckedExtrinsic::::decode( + &mut encoded_xt_opaque.as_slice(), + ) { + if self.is_shield_funds_function(&xt.function.0) { + let hash_of_xt = hash_of(&xt); + + match self.handle_shield_funds_xt(xt) { + Err(e) => { + error!("Error performing shield funds. Error: {:?}", e); + }, + Ok(_) => { + // Cache successfully executed shielding call. + executed_shielding_calls.push(hash_of_xt) + }, + } + } + } + + // Found CallWorker extrinsic in block. + // No else-if here! Because the same opaque extrinsic can contain multiple Fns at once (this lead to intermittent M6 failures) + if let Ok(xt) = ParentchainUncheckedExtrinsic::::decode( + &mut encoded_xt_opaque.as_slice(), + ) { + if self.is_call_worker_function(&xt.function.0) { + let (_, request) = xt.function; + let (shard, cypher_text) = (request.shard, request.cyphertext); + debug!("Found trusted call extrinsic, submitting it to the top pool"); + self.submit_trusted_call(shard, cypher_text); + } + } + + // litentry + // Found SetUserShieldingKey extrinsic + if let Ok(xt) = ParentchainUncheckedExtrinsic::::decode( + &mut encoded_xt_opaque.as_slice(), + ) { + if self.is_set_user_shielding_key_function(&xt.function.0) { + let (_, shard, encrypted_key) = xt.function; + let shielding_key = self.shielding_key_repo.retrieve_key()?; + + let key = UserShieldingKeyType::decode( + &mut shielding_key.decrypt(&encrypted_key)?.as_slice(), + )?; + + if let Some((multiaddress_account, _, _)) = xt.signature { + let account = AccountIdLookup::lookup(multiaddress_account)?; + let enclave_account_id = self.stf_enclave_signer.get_enclave_account()?; + let trusted_call = TrustedCall::set_user_shielding_key_runtime( + enclave_account_id, + account, + key, + ); + let signed_trusted_call = + self.stf_enclave_signer.sign_call_with_self(&trusted_call, &shard)?; + let trusted_operation = + TrustedOperation::indirect_call(signed_trusted_call); + + let encrypted_trusted_call = + shielding_key.encrypt(&trusted_operation.encode())?; + self.submit_trusted_call(shard, encrypted_trusted_call); + } + } + } + + // Found LinkIdentityFn extrinsic + if let Ok(xt) = ParentchainUncheckedExtrinsic::::decode( + &mut encoded_xt_opaque.as_slice(), + ) { + if self.is_link_identity_funciton(&xt.function.0) { + let (_, shard, encrypted_identity, encrypted_metadata) = xt.function; + let shielding_key = self.shielding_key_repo.retrieve_key()?; + + let identity: Identity = Identity::decode( + &mut shielding_key.decrypt(&encrypted_identity).unwrap().as_slice(), + )?; + let metadata = match encrypted_metadata { + None => None, + Some(m) => { + let decrypted_metadata = shielding_key.decrypt(&m)?; + Some(MetadataOf::::decode(&mut decrypted_metadata.as_slice())?) + }, + }; + + if let Some((multiaddress_account, _, _)) = xt.signature { + let account = AccountIdLookup::lookup(multiaddress_account)?; + let enclave_account_id = self.stf_enclave_signer.get_enclave_account()?; + let trusted_call = TrustedCall::link_identity_runtime( + enclave_account_id, + account, + identity, + metadata, + block_number + .try_into() + .map_err(|_| crate::error::Error::ConvertParentchainBlockNumber)?, + ); + let signed_trusted_call = + self.stf_enclave_signer.sign_call_with_self(&trusted_call, &shard)?; + let trusted_operation = + TrustedOperation::indirect_call(signed_trusted_call); + + let encrypted_trusted_call = + shielding_key.encrypt(&trusted_operation.encode())?; + self.submit_trusted_call(shard, encrypted_trusted_call); + } + } + } + + // Found UnlinkIdentityFn extrinsic + if let Ok(xt) = ParentchainUncheckedExtrinsic::::decode( + &mut encoded_xt_opaque.as_slice(), + ) { + if self.is_unlink_identity_funciton(&xt.function.0) { + let (_, shard, encrypted_identity) = xt.function; + let shielding_key = self.shielding_key_repo.retrieve_key()?; + + let identity: Identity = Identity::decode( + &mut shielding_key.decrypt(&encrypted_identity).unwrap().as_slice(), + )?; + + if let Some((multiaddress_account, _, _)) = xt.signature { + let account = AccountIdLookup::lookup(multiaddress_account)?; + let enclave_account_id = self.stf_enclave_signer.get_enclave_account()?; + let trusted_call = TrustedCall::unlink_identity_runtime( + enclave_account_id, + account, + identity, + ); + let signed_trusted_call = + self.stf_enclave_signer.sign_call_with_self(&trusted_call, &shard)?; + let trusted_operation = + TrustedOperation::indirect_call(signed_trusted_call); + + let encrypted_trusted_call = + shielding_key.encrypt(&trusted_operation.encode())?; + self.submit_trusted_call(shard, encrypted_trusted_call); + } + } + } + + // Found VerifyIdentity extrinsic + if let Ok(xt) = ParentchainUncheckedExtrinsic::::decode( + &mut encoded_xt_opaque.as_slice(), + ) { + if self.is_verify_identity_funciton(&xt.function.0) { + let (_, shard, encrypted_identity, encrypted_validation_data) = xt.function; + let shielding_key = self.shielding_key_repo.retrieve_key()?; + + let identity: Identity = Identity::decode( + &mut shielding_key.decrypt(&encrypted_identity).unwrap().as_slice(), + )?; + let validation_data = ValidationData::decode( + &mut shielding_key.decrypt(&encrypted_validation_data).unwrap().as_slice(), + )?; + + if let Some((multiaddress_account, _, _)) = xt.signature { + let account = AccountIdLookup::lookup(multiaddress_account)?; + let enclave_account_id = self.stf_enclave_signer.get_enclave_account()?; + let trusted_call = TrustedCall::verify_identity_preflight( + enclave_account_id, + account, + identity, + validation_data, + block_number + .try_into() + .map_err(|_| crate::error::Error::ConvertParentchainBlockNumber)?, + ); + let signed_trusted_call = + self.stf_enclave_signer.sign_call_with_self(&trusted_call, &shard)?; + let trusted_operation = + TrustedOperation::indirect_call(signed_trusted_call); + + let encrypted_trusted_call = + shielding_key.encrypt(&trusted_operation.encode())?; + self.submit_trusted_call(shard, encrypted_trusted_call); + } + } + } + } + + // Include a processed parentchain block confirmation for each block. + self.create_processed_parentchain_block_call::( + block_hash, + executed_shielding_calls, + block_number, + ) + } +} + +fn hash_of(xt: &T) -> H256 { + blake2_256(&xt.encode()).into() +} + +#[cfg(test)] +mod test { + use super::*; + use codec::Encode; + use itc_parentchain_test::parentchain_block_builder::ParentchainBlockBuilder; + use itp_node_api::{ + api_client::{ParentchainExtrinsicParams, ParentchainExtrinsicParamsBuilder}, + metadata::{metadata_mocks::NodeMetadataMock, provider::NodeMetadataRepository}, + }; + use itp_sgx_crypto::mocks::KeyRepositoryMock; + use itp_stf_executor::mocks::StfEnclaveSignerMock; + use itp_test::mock::shielding_crypto_mock::ShieldingCryptoMock; + use itp_top_pool_author::mocks::AuthorApiMock; + use itp_types::{Block, Request, ShardIdentifier}; + use sp_core::{ed25519, Pair}; + use sp_runtime::{MultiSignature, OpaqueExtrinsic}; + use std::assert_matches::assert_matches; + use substrate_api_client::{ExtrinsicParams, GenericAddress}; + + type TestShieldingKeyRepo = KeyRepositoryMock; + type TestStfEnclaveSigner = StfEnclaveSignerMock; + type TestTopPoolAuthor = AuthorApiMock; + type TestNodeMetadataRepository = NodeMetadataRepository; + type TestIndirectCallExecutor = IndirectCallsExecutor< + TestShieldingKeyRepo, + TestStfEnclaveSigner, + TestTopPoolAuthor, + TestNodeMetadataRepository, + >; + + type Seed = [u8; 32]; + const TEST_SEED: Seed = *b"12345678901234567890123456789012"; + + #[test] + fn indirect_call_can_be_added_to_pool_successfully() { + let _ = env_logger::builder().is_test(true).try_init(); + + let (indirect_calls_executor, top_pool_author, _) = + test_fixtures([0u8; 32], NodeMetadataMock::new()); + + let opaque_extrinsic = + OpaqueExtrinsic::from_bytes(call_worker_unchecked_extrinsic().encode().as_slice()) + .unwrap(); + + let parentchain_block = ParentchainBlockBuilder::default() + .with_extrinsics(vec![opaque_extrinsic]) + .build(); + + indirect_calls_executor + .execute_indirect_calls_in_extrinsics(&parentchain_block) + .unwrap(); + + assert_eq!(1, top_pool_author.pending_tops(shard_id()).unwrap().len()); + } + + #[test] + fn shielding_call_can_be_added_to_pool_successfully() { + let _ = env_logger::builder().is_test(true).try_init(); + + let mr_enclave = [33u8; 32]; + let (indirect_calls_executor, top_pool_author, shielding_key_repo) = + test_fixtures(mr_enclave.clone(), NodeMetadataMock::new()); + let shielding_key = shielding_key_repo.retrieve_key().unwrap(); + + let opaque_extrinsic = OpaqueExtrinsic::from_bytes( + shield_funds_unchecked_extrinsic(&shielding_key).encode().as_slice(), + ) + .unwrap(); + + let parentchain_block = ParentchainBlockBuilder::default() + .with_extrinsics(vec![opaque_extrinsic]) + .build(); + + indirect_calls_executor + .execute_indirect_calls_in_extrinsics(&parentchain_block) + .unwrap(); + + assert_eq!(1, top_pool_author.pending_tops(shard_id()).unwrap().len()); + let submitted_extrinsic = + top_pool_author.pending_tops(shard_id()).unwrap().first().cloned().unwrap(); + let decrypted_extrinsic = shielding_key.decrypt(&submitted_extrinsic).unwrap(); + let decoded_operation = + TrustedOperation::decode(&mut decrypted_extrinsic.as_slice()).unwrap(); + assert_matches!(decoded_operation, TrustedOperation::indirect_call(_)); + let trusted_call_signed = decoded_operation.to_call().unwrap(); + assert!(trusted_call_signed.verify_signature(&mr_enclave, &shard_id())); + } + + #[test] + fn ensure_empty_extrinsic_vec_triggers_zero_filled_merkle_root() { + // given + let dummy_metadata = NodeMetadataMock::new(); + let (indirect_calls_executor, _, _) = test_fixtures([38u8; 32], dummy_metadata.clone()); + + let block_hash = H256::from([1; 32]); + let extrinsics = Vec::new(); + let confirm_processed_parentchain_block_indexes = + dummy_metadata.confirm_processed_parentchain_block_call_indexes().unwrap(); + let expected_call = + (confirm_processed_parentchain_block_indexes, block_hash, 1, H256::default()).encode(); + + // when + let call = indirect_calls_executor + .create_processed_parentchain_block_call::(block_hash, extrinsics, 1) + .unwrap(); + + // then + assert_eq!(call.0, expected_call); + } + + #[test] + fn ensure_non_empty_extrinsic_vec_triggers_non_zero_merkle_root() { + // given + let dummy_metadata = NodeMetadataMock::new(); + let (indirect_calls_executor, _, _) = test_fixtures([39u8; 32], dummy_metadata.clone()); + + let block_hash = H256::from([1; 32]); + let extrinsics = vec![H256::from([4; 32]), H256::from([9; 32])]; + let confirm_processed_parentchain_block_indexes = + dummy_metadata.confirm_processed_parentchain_block_call_indexes().unwrap(); + + let zero_root_call = + (confirm_processed_parentchain_block_indexes, block_hash, 1, H256::default()).encode(); + + // when + let call = indirect_calls_executor + .create_processed_parentchain_block_call::(block_hash, extrinsics, 1) + .unwrap(); + + // then + assert_ne!(call.0, zero_root_call); + } + + fn shield_funds_unchecked_extrinsic( + shielding_key: &ShieldingCryptoMock, + ) -> ParentchainUncheckedExtrinsic { + let target_account = shielding_key.encrypt(&AccountId::new([2u8; 32]).encode()).unwrap(); + let dummy_metadata = NodeMetadataMock::new(); + + let shield_funds_indexes = dummy_metadata.shield_funds_call_indexes().unwrap(); + ParentchainUncheckedExtrinsic::::new_signed( + (shield_funds_indexes, target_account, 1000u128, shard_id()), + GenericAddress::Address32([1u8; 32]), + MultiSignature::Ed25519(default_signature()), + default_extrinsic_params().signed_extra(), + ) + } + + fn call_worker_unchecked_extrinsic() -> ParentchainUncheckedExtrinsic { + let request = Request { shard: shard_id(), cyphertext: vec![1u8, 2u8] }; + let dummy_metadata = NodeMetadataMock::new(); + let call_worker_indexes = dummy_metadata.call_worker_call_indexes().unwrap(); + + ParentchainUncheckedExtrinsic::::new_signed( + (call_worker_indexes, request), + GenericAddress::Address32([1u8; 32]), + MultiSignature::Ed25519(default_signature()), + default_extrinsic_params().signed_extra(), + ) + } + + fn default_signature() -> ed25519::Signature { + signer().sign(&[0u8]) + } + + fn signer() -> ed25519::Pair { + ed25519::Pair::from_seed(&TEST_SEED) + } + + fn shard_id() -> ShardIdentifier { + ShardIdentifier::default() + } + + fn default_extrinsic_params() -> ParentchainExtrinsicParams { + ParentchainExtrinsicParams::new( + 0, + 0, + 0, + H256::default(), + ParentchainExtrinsicParamsBuilder::default(), + ) + } + fn test_fixtures( + mr_enclave: [u8; 32], + metadata: NodeMetadataMock, + ) -> (TestIndirectCallExecutor, Arc, Arc) { + let shielding_key_repo = Arc::new(TestShieldingKeyRepo::default()); + let stf_enclave_signer = Arc::new(TestStfEnclaveSigner::new(mr_enclave)); + let top_pool_author = Arc::new(TestTopPoolAuthor::default()); + let node_metadata_repo = Arc::new(NodeMetadataRepository::new(metadata)); + + let executor = IndirectCallsExecutor::new( + shielding_key_repo.clone(), + stf_enclave_signer, + top_pool_author.clone(), + node_metadata_repo, + ); + + (executor, top_pool_author, shielding_key_repo) + } +} diff --git a/tee-worker/core/parentchain/indirect-calls-executor/src/lib.rs b/tee-worker/core/parentchain/indirect-calls-executor/src/lib.rs new file mode 100644 index 0000000000..1ccab8f33a --- /dev/null +++ b/tee-worker/core/parentchain/indirect-calls-executor/src/lib.rs @@ -0,0 +1,37 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ +//! Indirect calls execution logic. +#![feature(trait_alias)] +#![cfg_attr(not(feature = "std"), no_std)] +#![cfg_attr(test, feature(assert_matches))] + +#[cfg(all(feature = "std", feature = "sgx"))] +compile_error!("feature \"std\" and feature \"sgx\" cannot be enabled at the same time"); + +#[cfg(all(not(feature = "std"), feature = "sgx"))] +extern crate sgx_tstd as std; + +// re-export module to properly feature gate sgx and regular std environment +#[cfg(all(not(feature = "std"), feature = "sgx"))] +pub mod sgx_reexport_prelude { + pub use futures_sgx as futures; + pub use thiserror_sgx as thiserror; +} +pub mod error; +pub mod indirect_calls_executor; + +pub use indirect_calls_executor::*; diff --git a/tee-worker/core/parentchain/light-client/Cargo.toml b/tee-worker/core/parentchain/light-client/Cargo.toml new file mode 100644 index 0000000000..d1572f8a2b --- /dev/null +++ b/tee-worker/core/parentchain/light-client/Cargo.toml @@ -0,0 +1,75 @@ +[package] +authors = ["Integritee AG "] +edition = "2021" +name = "itc-parentchain-light-client" +version = "0.9.0" + +[dependencies] +codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive", "chain-error"] } +derive_more = { version = "0.99.5" } +finality-grandpa = { version = "0.16.0", default-features = false, features = ["derive-codec"] } +hash-db = { version = "0.15.2", default-features = false } +lazy_static = { version = "1.1.0", features = ["spin_no_std"] } +log = { version = "0.4", default-features = false } +num = { package = "num-traits", version = "0.2", default-features = false } +thiserror = { version = "1.0.26", optional = true } + +# sgx-deps +sgx_tstd = { branch = "master", git = "https://github.com/apache/teaclave-sgx-sdk.git", features = ["untrusted_fs"], optional = true } +sgx_types = { branch = "master", git = "https://github.com/apache/teaclave-sgx-sdk.git" } +thiserror-sgx = { package = "thiserror", git = "https://github.com/mesalock-linux/thiserror-sgx", tag = "sgx_1.1.3", optional = true } + +# local deps +itp-ocall-api = { path = "../../../core-primitives/ocall-api", default-features = false } +itp-settings = { path = "../../../core-primitives/settings" } +itp-sgx-io = { path = "../../../core-primitives/sgx/io", default-features = false } +itp-storage = { path = "../../../core-primitives/storage", default-features = false } +itp-types = { path = "../../../core-primitives/types", default-features = false } + +# substrate deps +frame-system = { default-features = false, git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.29" } +sp-application-crypto = { default-features = false, features = ["full_crypto"], git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.29" } +sp-core = { default-features = false, features = ["full_crypto"], git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.29" } +sp-finality-grandpa = { default-features = false, git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.29" } +sp-runtime = { default-features = false, git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.29" } +sp-trie = { default-features = false, git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.29" } + +# mocks dependencies +itc-parentchain-test = { optional = true, default-features = false, path = "../../../core/parentchain/test" } + +[dev-dependencies] +itp-test = { path = "../../../core-primitives/test" } + +[features] +default = ["std"] +mocks = [ + "itc-parentchain-test", +] +sgx = [ + "sgx_tstd", + "thiserror-sgx", + "itp-sgx-io/sgx", + "itp-storage/sgx", +] +std = [ + "codec/std", + "hash-db/std", + "num/std", + "log/std", + "finality-grandpa/std", + "thiserror", + + # substrate deps + "frame-system/std", + "sp-core/std", + "sp-application-crypto/std", + "sp-finality-grandpa/std", + "sp-runtime/std", + "sp-trie/std", + + # local deps + "itp-ocall-api/std", + "itp-storage/std", + "itp-sgx-io/std", + "itp-types/std", +] diff --git a/tee-worker/core/parentchain/light-client/src/concurrent_access.rs b/tee-worker/core/parentchain/light-client/src/concurrent_access.rs new file mode 100644 index 0000000000..f791697781 --- /dev/null +++ b/tee-worker/core/parentchain/light-client/src/concurrent_access.rs @@ -0,0 +1,147 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +//! Concurrent access mechanisms that ensure mutually exclusive read/write access +//! to the light-client (validator) by employing RwLocks under the hood. + +#[cfg(feature = "sgx")] +use std::sync::SgxRwLock as RwLock; + +#[cfg(feature = "std")] +use std::sync::RwLock; + +use crate::{ + error::{Error, Result}, + ExtrinsicSender as ExtrinsicSenderTrait, LightClientState, LightValidationState, + Validator as ValidatorTrait, +}; +use finality_grandpa::BlockNumberOps; +use itp_sgx_io::StaticSealedIO; +use sp_runtime::traits::{Block as ParentchainBlockTrait, NumberFor}; +use std::marker::PhantomData; + +/// Retrieve an exclusive lock on a validator for either read or write access. +/// +/// In order to hide the whole locks mechanics, we provide an interface that allows executing +/// either a mutating, or a non-mutating function on the validator. +/// The reason we have this additional wrapper around `SealedIO`, is that we need +/// to guard against concurrent access by using RWLocks (which `SealedIO` does not do). +pub trait ValidatorAccess +where + ParentchainBlock: ParentchainBlockTrait, + NumberFor: BlockNumberOps, +{ + type ValidatorType: ValidatorTrait + + LightClientState + + ExtrinsicSenderTrait; + + /// Execute a non-mutating function on the validator. + fn execute_on_validator(&self, getter_function: F) -> Result + where + F: FnOnce(&Self::ValidatorType) -> Result; + + /// Execute a mutating function on the validator. + fn execute_mut_on_validator(&self, mutating_function: F) -> Result + where + F: FnOnce(&mut Self::ValidatorType) -> Result; +} + +/// Implementation of a validator access based on a global lock and corresponding file. +#[derive(Debug)] +pub struct ValidatorAccessor +where + Validator: ValidatorTrait + + LightClientState + + ExtrinsicSenderTrait, + Seal: StaticSealedIO>, + ParentchainBlock: ParentchainBlockTrait, + NumberFor: BlockNumberOps, +{ + light_validation: RwLock, + _phantom: PhantomData<(Seal, Validator, ParentchainBlock)>, +} + +impl ValidatorAccessor +where + Validator: ValidatorTrait + + LightClientState + + ExtrinsicSenderTrait, + Seal: StaticSealedIO>, + ParentchainBlock: ParentchainBlockTrait, + NumberFor: BlockNumberOps, +{ + pub fn new(validator: Validator) -> Self { + ValidatorAccessor { light_validation: RwLock::new(validator), _phantom: Default::default() } + } +} + +impl ValidatorAccess + for ValidatorAccessor +where + Validator: ValidatorTrait + + LightClientState + + ExtrinsicSenderTrait, + Seal: StaticSealedIO>, + ParentchainBlock: ParentchainBlockTrait, + NumberFor: BlockNumberOps, +{ + type ValidatorType = Validator; + + fn execute_on_validator(&self, getter_function: F) -> Result + where + F: FnOnce(&Self::ValidatorType) -> Result, + { + let mut light_validation_lock = + self.light_validation.write().map_err(|_| Error::PoisonedLock)?; + let state = Seal::unseal_from_static_file()?; + light_validation_lock.set_state(state); + getter_function(&light_validation_lock) + } + + fn execute_mut_on_validator(&self, mutating_function: F) -> Result + where + F: FnOnce(&mut Self::ValidatorType) -> Result, + { + let mut light_validation_lock = + self.light_validation.write().map_err(|_| Error::PoisonedLock)?; + let state = Seal::unseal_from_static_file()?; + light_validation_lock.set_state(state); + let result = mutating_function(&mut light_validation_lock); + Seal::seal_to_static_file(light_validation_lock.get_state())?; + result + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::mocks::{ + validator_mock::ValidatorMock, validator_mock_seal::LightValidationStateSealMock, + }; + use itp_types::Block; + + type TestAccessor = ValidatorAccessor; + + #[test] + fn execute_with_and_without_mut_in_single_thread_works() { + let validator_mock = ValidatorMock::default(); + let accessor = TestAccessor::new(validator_mock); + + let _read_result = accessor.execute_on_validator(|_v| Ok(())).unwrap(); + let _write_result = accessor.execute_mut_on_validator(|_v| Ok(())).unwrap(); + } +} diff --git a/tee-worker/core/parentchain/light-client/src/error.rs b/tee-worker/core/parentchain/light-client/src/error.rs new file mode 100644 index 0000000000..6cc740ad16 --- /dev/null +++ b/tee-worker/core/parentchain/light-client/src/error.rs @@ -0,0 +1,84 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +use std::{boxed::Box, string::String}; + +use sgx_types::sgx_status_t; +#[cfg(all(not(feature = "std"), feature = "sgx"))] +use thiserror_sgx as thiserror; + +pub type Result = core::result::Result; + +/// Substrate Client error +#[derive(Debug, PartialEq, Eq, thiserror::Error)] +pub enum JustificationError { + #[error("Error decoding justification")] + JustificationDecode, + /// Justification for header is correctly encoded, but invalid. + #[error("bad justification for header: {0}")] + BadJustification(String), + #[error("Invalid authorities set")] + InvalidAuthoritiesSet, +} + +#[derive(Debug, thiserror::Error)] +pub enum Error { + #[error(transparent)] + Storage(#[from] itp_storage::Error), + #[error("Validator set mismatch")] + ValidatorSetMismatch, + #[error("Invalid ancestry proof")] + InvalidAncestryProof, + #[error("No such relay exists")] + NoSuchRelayExists, + #[error("Invalid Finality Proof: {0}")] + InvalidFinalityProof(#[from] JustificationError), + #[error("Header ancestry mismatch")] + HeaderAncestryMismatch, + #[error("Poisoned validator lock")] + PoisonedLock, + #[error("No Justification found")] + NoJustificationFound, + #[error(transparent)] + Other(#[from] Box), +} + +impl From for Error { + fn from(e: std::io::Error) -> Self { + Self::Other(e.into()) + } +} + +impl From for Error { + #[cfg(feature = "std")] + fn from(e: codec::Error) -> Self { + Self::Other(e.into()) + } + + #[cfg(not(feature = "std"))] + fn from(e: codec::Error) -> Self { + Self::Other(format!("{:?}", e).into()) + } +} + +impl From for sgx_status_t { + /// return sgx_status for top level enclave functions + fn from(error: Error) -> sgx_status_t { + log::warn!("LightClientError into sgx_status_t: {:?}", error); + sgx_status_t::SGX_ERROR_UNEXPECTED + } +} diff --git a/tee-worker/core/parentchain/light-client/src/finality.rs b/tee-worker/core/parentchain/light-client/src/finality.rs new file mode 100644 index 0000000000..7fae7897ba --- /dev/null +++ b/tee-worker/core/parentchain/light-client/src/finality.rs @@ -0,0 +1,187 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +//! Finality for determination of the light client validation. + +use crate::{ + error::Result, + grandpa_log, + justification::GrandpaJustification, + state::{RelayState, ScheduledChangeAtBlock}, + AuthorityList, Error, NumberFor, +}; +use finality_grandpa::voter_set::VoterSet; +use log::*; +pub use sp_finality_grandpa::SetId; +use sp_finality_grandpa::{AuthorityId, ScheduledChange, GRANDPA_ENGINE_ID}; +use sp_runtime::{ + generic::Digest, + traits::{Block as ParentchainBlockTrait, Header as HeaderTrait}, + Justification, Justifications, +}; + +#[derive(Default)] +pub struct GrandpaFinality; + +#[derive(Default)] +pub struct ParachainFinality; + +pub trait Finality { + fn validate( + &self, + header: Block::Header, + validator_set: &AuthorityList, + validator_set_id: SetId, + justifications: Option, + relay: &mut RelayState, + ) -> Result<()>; +} + +impl Finality for ParachainFinality +where + Block: ParentchainBlockTrait, +{ + fn validate( + &self, + _header: Block::Header, + _validator_set: &AuthorityList, + _validator_set_id: SetId, + _justifications: Option, + _relay: &mut RelayState, + ) -> Result<()> { + Ok(()) + } +} + +impl Finality for GrandpaFinality +where + Block: ParentchainBlockTrait, + NumberFor: finality_grandpa::BlockNumberOps, +{ + fn validate( + &self, + header: Block::Header, + validator_set: &AuthorityList, + validator_set_id: SetId, + justifications: Option, + relay: &mut RelayState, + ) -> Result<()> { + Self::apply_validator_set_change(relay, &header); + + // Check that the header has been finalized + let voter_set = + VoterSet::new(validator_set.clone().into_iter()).expect("VoterSet may not be empty"); + + // ensure justifications is a grandpa justification + let grandpa_justification = + justifications.and_then(|just| just.into_justification(GRANDPA_ENGINE_ID)); + + let block_hash = header.hash(); + let block_num = *header.number(); + + match grandpa_justification { + Some(justification) => { + if let Err(err) = Self::verify_grandpa_proof::( + (GRANDPA_ENGINE_ID, justification), + block_hash, + block_num, + validator_set_id, + &voter_set, + ) { + // FIXME: Printing error upon invalid justification, but this will need a better fix + // see issue #353 + error!("Block {:?} contained invalid justification: {:?}", block_num, err); + relay.unjustified_headers.push(header.hash()); + relay.set_last_finalized_block_header(header); + return Err(err) + } + Self::schedule_validator_set_change(relay, &header); + + Ok(()) + }, + None => { + relay.unjustified_headers.push(header.hash()); + relay.set_last_finalized_block_header(header); + + debug!( + "Syncing finalized block without grandpa proof. Amount of unjustified headers: {}", + relay.unjustified_headers.len() + ); + Err(Error::NoJustificationFound) + }, + } + } +} + +impl GrandpaFinality { + fn apply_validator_set_change( + relay: &mut RelayState, + header: &Block::Header, + ) { + if let Some(change) = relay.scheduled_change.take() { + if &change.at_block == header.number() { + relay.current_validator_set = change.next_authority_list; + relay.current_validator_set_id += 1; + } + } + } + + fn schedule_validator_set_change( + relay: &mut RelayState, + header: &Block::Header, + ) { + if let Some(log) = pending_change::(header.digest()) { + if relay.scheduled_change.is_some() { + error!( + "Tried to scheduled authorities change even though one is already scheduled!!" + ); // should not happen if blockchain is configured properly + } else { + relay.scheduled_change = Some(ScheduledChangeAtBlock { + at_block: log.delay + *header.number(), + next_authority_list: log.next_authorities, + }) + } + } + } + + fn verify_grandpa_proof( + justification: Justification, + hash: Block::Hash, + number: NumberFor, + set_id: u64, + voters: &VoterSet, + ) -> Result<()> + where + NumberFor: finality_grandpa::BlockNumberOps, + { + // We don't really care about the justification, as long as it's valid + let _ = GrandpaJustification::::decode_and_verify_finalizes( + &justification.1, + (hash, number), + set_id, + voters, + )?; + + Ok(()) + } +} + +fn pending_change( + digest: &Digest, +) -> Option>> { + grandpa_log::(digest).and_then(|log| log.try_into_change()) +} diff --git a/tee-worker/core/parentchain/light-client/src/io.rs b/tee-worker/core/parentchain/light-client/src/io.rs new file mode 100644 index 0000000000..c0e5d06642 --- /dev/null +++ b/tee-worker/core/parentchain/light-client/src/io.rs @@ -0,0 +1,170 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +use crate::{ + error::Result, + finality::{Finality, GrandpaFinality, ParachainFinality}, + light_client_init_params::{GrandpaParams, SimpleParams}, + light_validation::LightValidation, + Error, LightValidationState, NumberFor, Validator, +}; +use codec::{Decode, Encode}; +use core::fmt::Debug; +use itp_ocall_api::EnclaveOnChainOCallApi; +use itp_settings::files::LIGHT_CLIENT_DB; +use itp_sgx_io::{seal, unseal, StaticSealedIO}; +use log::*; +use sp_finality_grandpa::AuthorityList; +use sp_runtime::traits::{Block, Header}; +use std::{boxed::Box, fs, sgxfs::SgxFile, sync::Arc}; + +#[derive(Copy, Clone, Debug)] +pub struct LightClientStateSeal { + _phantom: (B, LightClientState), +} + +impl StaticSealedIO + for LightClientStateSeal +{ + type Error = Error; + type Unsealed = LightClientState; + + fn unseal_from_static_file() -> Result { + Ok(unseal(LIGHT_CLIENT_DB).map(|b| Decode::decode(&mut b.as_slice()))??) + } + + fn seal_to_static_file(unsealed: &Self::Unsealed) -> Result<()> { + debug!("backup light client state"); + if fs::copy(LIGHT_CLIENT_DB, format!("{}.1", LIGHT_CLIENT_DB)).is_err() { + warn!("could not backup previous light client state"); + }; + debug!("Seal light client State. Current state: {:?}", unsealed); + Ok(unsealed.using_encoded(|bytes| seal(bytes, LIGHT_CLIENT_DB))?) + } +} + +// FIXME: This is a lot of duplicate code for the initialization of two +// different but sameish light clients. Should be tackled with #1081 +pub fn read_or_init_grandpa_validator( + params: GrandpaParams, + ocall_api: Arc, +) -> Result> +where + B: Block, + NumberFor: finality_grandpa::BlockNumberOps, + OCallApi: EnclaveOnChainOCallApi, +{ + // FIXME: That should be an unique path. + if SgxFile::open(LIGHT_CLIENT_DB).is_err() { + info!("[Enclave] ChainRelay DB not found, creating new! {}", LIGHT_CLIENT_DB); + return init_grandpa_validator::(params, ocall_api) + } + + let (validation_state, genesis_hash) = get_validation_state::()?; + + let mut validator = init_grandpa_validator::(params.clone(), ocall_api)?; + + if genesis_hash == params.genesis_header.hash() { + validator.set_state(validation_state); + info!("Found already initialized light client with Genesis Hash: {:?}", genesis_hash); + } + info!("light client state: {:?}", validator); + Ok(validator) +} + +pub fn read_or_init_parachain_validator( + params: SimpleParams, + ocall_api: Arc, +) -> Result> +where + B: Block, + NumberFor: finality_grandpa::BlockNumberOps, + OCallApi: EnclaveOnChainOCallApi, +{ + // FIXME: That should be an unique path. + if SgxFile::open(LIGHT_CLIENT_DB).is_err() { + info!("[Enclave] ChainRelay DB not found, creating new! {}", LIGHT_CLIENT_DB); + return init_parachain_validator::(params, ocall_api) + } + + let (validation_state, genesis_hash) = get_validation_state::()?; + + let mut validator = init_parachain_validator::(params.clone(), ocall_api)?; + + if genesis_hash == params.genesis_header.hash() { + validator.set_state(validation_state); + info!("Found already initialized light client with Genesis Hash: {:?}", genesis_hash); + } + info!("light client state: {:?}", validator); + Ok(validator) +} + +fn get_validation_state() -> Result<(LightValidationState, B::Hash)> +where + B: Block, +{ + let validation_state = + LightClientStateSeal::>::unseal_from_static_file()?; + + let relay = validation_state + .tracked_relays + .get(&validation_state.num_relays) + .ok_or(Error::NoSuchRelayExists)?; + let genesis_hash = relay.header_hashes[0]; + + Ok((validation_state, genesis_hash)) +} + +fn init_grandpa_validator( + params: GrandpaParams, + ocall_api: Arc, +) -> Result> +where + B: Block, + NumberFor: finality_grandpa::BlockNumberOps, + OCallApi: EnclaveOnChainOCallApi, +{ + let finality: Arc + Sync + Send + 'static>> = + Arc::new(Box::new(GrandpaFinality {})); + let mut validator = LightValidation::::new(ocall_api, finality); + validator.initialize_grandpa_relay( + params.genesis_header, + params.authorities, + params.authority_proof, + )?; + + LightClientStateSeal::>::seal_to_static_file(validator.get_state())?; + Ok(validator) +} + +fn init_parachain_validator( + params: SimpleParams, + ocall_api: Arc, +) -> Result> +where + B: Block, + NumberFor: finality_grandpa::BlockNumberOps, + OCallApi: EnclaveOnChainOCallApi, +{ + let finality: Arc + Sync + Send + 'static>> = + Arc::new(Box::new(ParachainFinality {})); + let mut validator = LightValidation::::new(ocall_api, finality); + validator.initialize_parachain_relay(params.genesis_header, AuthorityList::default())?; + + LightClientStateSeal::>::seal_to_static_file(validator.get_state())?; + Ok(validator) +} diff --git a/tee-worker/core/parentchain/light-client/src/justification.rs b/tee-worker/core/parentchain/light-client/src/justification.rs new file mode 100644 index 0000000000..328134a829 --- /dev/null +++ b/tee-worker/core/parentchain/light-client/src/justification.rs @@ -0,0 +1,209 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +use std::{ + collections::{HashMap, HashSet}, + string::ToString, + vec::Vec, +}; + +use super::error::JustificationError as ClientError; +use codec::{Decode, Encode}; +use finality_grandpa::{voter_set::VoterSet, Error as GrandpaError}; +use log::*; +use sp_finality_grandpa::{AuthorityId, AuthorityList, AuthoritySignature}; +use sp_runtime::traits::{Block as BlockT, Header as HeaderT, NumberFor}; + +/// A commit message for this chain's block type. +pub type Commit = finality_grandpa::Commit< + ::Hash, + NumberFor, + AuthoritySignature, + AuthorityId, +>; + +/// A GRANDPA justification for block finality, it includes a commit message and +/// an ancestry proof including all headers routing all precommit target blocks +/// to the commit target block. Due to the current voting strategy the precommit +/// targets should be the same as the commit target, since honest voters don't +/// vote past authority set change blocks. +/// +/// This is meant to be stored in the db and passed around the network to other +/// nodes, and are used by syncing nodes to prove authority set handoffs. +#[derive(Clone, Encode, Decode, PartialEq, Eq)] +pub struct GrandpaJustification { + round: u64, + pub(crate) commit: Commit, + votes_ancestries: Vec, +} + +impl GrandpaJustification { + /// Decode a GRANDPA justification and validate the commit and the votes' + /// ancestry proofs finalize the given block. + pub fn decode_and_verify_finalizes( + encoded: &[u8], + finalized_target: (Block::Hash, NumberFor), + set_id: u64, + voters: &VoterSet, + ) -> Result, ClientError> + where + NumberFor: finality_grandpa::BlockNumberOps, + { + let justification = GrandpaJustification::::decode(&mut &*encoded) + .map_err(|_| ClientError::JustificationDecode)?; + + if (justification.commit.target_hash, justification.commit.target_number) + != finalized_target + { + let msg = "invalid commit target in grandpa justification".to_string(); + Err(ClientError::BadJustification(msg)) + } else { + justification.verify_with_voter_set(set_id, voters).map(|_| justification) + } + } + + /// Validate the commit and the votes' ancestry proofs. + pub fn verify(&self, set_id: u64, authorities: AuthorityList) -> Result<(), ClientError> + where + NumberFor: finality_grandpa::BlockNumberOps, + { + let voters = + VoterSet::new(authorities.into_iter()).ok_or(ClientError::InvalidAuthoritiesSet)?; + + self.verify_with_voter_set(set_id, &voters) + } + + /// Validate the commit and the votes' ancestry proofs. + pub(crate) fn verify_with_voter_set( + &self, + set_id: u64, + voters: &VoterSet, + ) -> Result<(), ClientError> + where + NumberFor: finality_grandpa::BlockNumberOps, + { + use finality_grandpa::Chain; + + let ancestry_chain = AncestryChain::::new(&self.votes_ancestries); + + match finality_grandpa::validate_commit(&self.commit, voters, &ancestry_chain) { + Ok(ref result) if result.is_valid() => {}, + _ => { + let msg = "invalid commit in grandpa justification".to_string(); + return Err(ClientError::BadJustification(msg)) + }, + } + + let mut buf = Vec::new(); + let mut visited_hashes = HashSet::new(); + for signed in self.commit.precommits.iter() { + if !sp_finality_grandpa::check_message_signature_with_buffer( + &finality_grandpa::Message::Precommit(signed.precommit.clone()), + &signed.id, + &signed.signature, + self.round, + set_id, + &mut buf, + ) { + debug!("Bad signature on message from {:?}", &signed.id); + return Err(ClientError::BadJustification( + "invalid signature for precommit in grandpa justification".to_string(), + )) + } + + if self.commit.target_hash == signed.precommit.target_hash { + continue + } + + match ancestry_chain.ancestry(self.commit.target_hash, signed.precommit.target_hash) { + Ok(route) => { + // ancestry starts from parent hash but the precommit target hash has been visited + visited_hashes.insert(signed.precommit.target_hash); + for hash in route { + visited_hashes.insert(hash); + } + }, + _ => + return Err(ClientError::BadJustification( + "invalid precommit ancestry proof in grandpa justification".to_string(), + )), + } + } + + let ancestry_hashes = + self.votes_ancestries.iter().map(|h: &Block::Header| h.hash()).collect(); + + if visited_hashes != ancestry_hashes { + return Err(ClientError::BadJustification( + "invalid precommit ancestries in grandpa justification with unused headers" + .to_string(), + )) + } + + Ok(()) + } + + /// The target block number and hash that this justifications proves finality for. + pub fn target(&self) -> (NumberFor, Block::Hash) { + (self.commit.target_number, self.commit.target_hash) + } +} + +/// A utility trait implementing `finality_grandpa::Chain` using a given set of headers. +/// This is useful when validating commits, using the given set of headers to +/// verify a valid ancestry route to the target commit block. +struct AncestryChain { + ancestry: HashMap, +} + +impl AncestryChain { + fn new(ancestry: &[Block::Header]) -> AncestryChain { + let ancestry: HashMap<_, _> = + ancestry.iter().cloned().map(|h: Block::Header| (h.hash(), h)).collect(); + + AncestryChain { ancestry } + } +} + +impl finality_grandpa::Chain> for AncestryChain +where + NumberFor: finality_grandpa::BlockNumberOps, +{ + fn ancestry( + &self, + base: Block::Hash, + block: Block::Hash, + ) -> Result, GrandpaError> { + let mut route = Vec::new(); + let mut current_hash = block; + loop { + if current_hash == base { + break + } + match self.ancestry.get(¤t_hash) { + Some(current_header) => { + current_hash = *current_header.parent_hash(); + route.push(current_hash); + }, + _ => return Err(GrandpaError::NotDescendent), + } + } + route.pop(); // remove the base + + Ok(route) + } +} diff --git a/tee-worker/core/parentchain/light-client/src/lib.rs b/tee-worker/core/parentchain/light-client/src/lib.rs new file mode 100644 index 0000000000..84ee3fecb1 --- /dev/null +++ b/tee-worker/core/parentchain/light-client/src/lib.rs @@ -0,0 +1,127 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +//! Light-client crate that imports and verifies parentchain blocks. + +#![allow(unused)] +#![cfg_attr(not(feature = "std"), no_std)] + +#[cfg(all(feature = "std", feature = "sgx"))] +compile_error!("feature \"std\" and feature \"sgx\" cannot be enabled at the same time"); + +#[cfg(all(not(feature = "std"), feature = "sgx"))] +#[macro_use] +extern crate sgx_tstd as std; + +// Re-export useful types. +pub use finality_grandpa::BlockNumberOps; +pub use sp_finality_grandpa::{AuthorityList, SetId}; + +use crate::light_validation_state::LightValidationState; +use error::Error; +use itp_storage::StorageProof; +use sp_finality_grandpa::{AuthorityId, AuthorityWeight, ConsensusLog, GRANDPA_ENGINE_ID}; +use sp_runtime::{ + generic::{Digest, OpaqueDigestItemId, SignedBlock}, + traits::{Block as ParentchainBlockTrait, Header as HeaderTrait}, + OpaqueExtrinsic, +}; +use std::vec::Vec; + +pub mod concurrent_access; +pub mod error; +pub mod finality; +pub mod justification; +pub mod light_client_init_params; +pub mod light_validation; +pub mod light_validation_state; +pub mod state; + +#[cfg(all(not(feature = "std"), feature = "sgx"))] +pub mod io; + +#[cfg(any(test, feature = "mocks"))] +pub mod mocks; + +pub type RelayId = u64; + +pub type AuthorityListRef<'a> = &'a [(AuthorityId, AuthorityWeight)]; + +// disambiguate associated types +/// Block number type +pub type NumberFor = <::Header as HeaderTrait>::Number; +/// Hash type of Block +pub type HashFor = <::Header as HeaderTrait>::Hash; +/// Hashing function used to produce `HashOf` +pub type HashingFor = <::Header as HeaderTrait>::Hashing; + +/// Validator trait +pub trait Validator +where + NumberFor: finality_grandpa::BlockNumberOps, +{ + fn initialize_grandpa_relay( + &mut self, + block_header: Block::Header, + validator_set: AuthorityList, + validator_set_proof: StorageProof, + ) -> Result; + + fn initialize_parachain_relay( + &mut self, + block_header: Block::Header, + validator_set: AuthorityList, + ) -> Result; + + fn submit_block( + &mut self, + relay_id: RelayId, + signed_block: &SignedBlock, + ) -> Result<(), Error>; + + fn check_xt_inclusion(&mut self, relay_id: RelayId, block: &Block) -> Result<(), Error>; + + fn set_state(&mut self, state: LightValidationState); + + fn get_state(&self) -> &LightValidationState; +} + +pub trait ExtrinsicSender { + /// Sends encoded extrinsics to the parentchain and cache them internally for later confirmation. + fn send_extrinsics(&mut self, extrinsics: Vec) -> Result<(), Error>; +} + +pub trait LightClientState { + fn num_xt_to_be_included(&self, relay_id: RelayId) -> Result; + + fn genesis_hash(&self, relay_id: RelayId) -> Result, Error>; + + fn latest_finalized_header(&self, relay_id: RelayId) -> Result; + + // Todo: Check if we still need this after #423 + fn penultimate_finalized_block_header(&self, relay_id: RelayId) + -> Result; + + fn num_relays(&self) -> RelayId; +} + +pub fn grandpa_log( + digest: &Digest, +) -> Option>> { + let id = OpaqueDigestItemId::Consensus(&GRANDPA_ENGINE_ID); + digest.convert_first(|l| l.try_to::>>(id)) +} diff --git a/tee-worker/core/parentchain/light-client/src/light_client_init_params.rs b/tee-worker/core/parentchain/light-client/src/light_client_init_params.rs new file mode 100644 index 0000000000..11cba5e5e0 --- /dev/null +++ b/tee-worker/core/parentchain/light-client/src/light_client_init_params.rs @@ -0,0 +1,32 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + Copyright (C) 2017-2019 Baidu, Inc. All Rights Reserved. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +use codec::{Decode, Encode}; +use sp_finality_grandpa::AuthorityList; +use std::vec::Vec; + +#[derive(Encode, Decode, Clone)] +pub struct GrandpaParams

{ + pub genesis_header: Header, + pub authorities: AuthorityList, + pub authority_proof: Vec>, +} +#[derive(Encode, Decode, Clone)] +pub struct SimpleParams
{ + pub genesis_header: Header, +} diff --git a/tee-worker/core/parentchain/light-client/src/light_validation.rs b/tee-worker/core/parentchain/light-client/src/light_validation.rs new file mode 100644 index 0000000000..fde36fdb47 --- /dev/null +++ b/tee-worker/core/parentchain/light-client/src/light_validation.rs @@ -0,0 +1,364 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +//! Light-client validation crate that verifies parentchain blocks. + +use crate::{ + error::Error, finality::Finality, light_validation_state::LightValidationState, + state::RelayState, AuthorityList, AuthorityListRef, ExtrinsicSender, HashFor, HashingFor, + LightClientState, NumberFor, RelayId, Validator, +}; +use codec::Encode; +use core::iter::Iterator; +use itp_ocall_api::EnclaveOnChainOCallApi; +use itp_storage::{Error as StorageError, StorageProof, StorageProofChecker}; +use log::*; +use sp_runtime::{ + generic::SignedBlock, + traits::{Block as ParentchainBlockTrait, Hash as HashTrait, Header as HeaderTrait}, + Justifications, OpaqueExtrinsic, +}; +use std::{boxed::Box, fmt, sync::Arc, vec::Vec}; + +#[derive(Clone)] +pub struct LightValidation { + light_validation_state: LightValidationState, + ocall_api: Arc, + finality: Arc + Sync + Send + 'static>>, +} + +impl + LightValidation +{ + pub fn new( + ocall_api: Arc, + finality: Arc + Sync + Send + 'static>>, + ) -> Self { + Self { light_validation_state: LightValidationState::new(), ocall_api, finality } + } + + fn initialize_relay( + &mut self, + block_header: Block::Header, + validator_set: AuthorityList, + ) -> Result { + let relay_info = RelayState::new(block_header, validator_set); + + let new_relay_id = self.light_validation_state.num_relays + 1; + self.light_validation_state.tracked_relays.insert(new_relay_id, relay_info); + + self.light_validation_state.num_relays = new_relay_id; + + Ok(new_relay_id) + } + + fn check_validator_set_proof( + state_root: &HashFor, + proof: StorageProof, + validator_set: AuthorityListRef, + ) -> Result<(), Error> { + let checker = StorageProofChecker::>::new(*state_root, proof)?; + + // By encoding the given set we should have an easy way to compare + // with the stuff we get out of storage via `read_value` + let mut encoded_validator_set = validator_set.encode(); + encoded_validator_set.insert(0, 1); // Add AUTHORITIES_VERISON == 1 + let actual_validator_set = checker + .read_value(b":grandpa_authorities")? + .ok_or(StorageError::StorageValueUnavailable)?; + + if encoded_validator_set == actual_validator_set { + Ok(()) + } else { + Err(Error::ValidatorSetMismatch) + } + } + + // A naive way to check whether a `child` header is a descendant + // of an `ancestor` header. For this it requires a proof which + // is a chain of headers between (but not including) the `child` + // and `ancestor`. This could be updated to use something like + // Log2 Ancestors (#2053) in the future. + fn verify_ancestry( + proof: Vec, + ancestor_hash: HashFor, + child: &Block::Header, + ) -> Result<(), Error> { + let mut parent_hash = child.parent_hash(); + if *parent_hash == ancestor_hash { + return Ok(()) + } + + // If we find that the header's parent hash matches our ancestor's hash we're done + for header in proof.iter() { + // Need to check that blocks are actually related + if header.hash() != *parent_hash { + break + } + + parent_hash = header.parent_hash(); + if *parent_hash == ancestor_hash { + return Ok(()) + } + } + + Err(Error::InvalidAncestryProof) + } + + fn submit_finalized_headers( + &mut self, + relay_id: RelayId, + header: Block::Header, + ancestry_proof: Vec, + justifications: Option, + ) -> Result<(), Error> { + let relay = self + .light_validation_state + .tracked_relays + .get_mut(&relay_id) + .ok_or(Error::NoSuchRelayExists)?; + + let validator_set = relay.current_validator_set.clone(); + let validator_set_id = relay.current_validator_set_id; + + // Check that the new header is a descendant of the old header + let last_header = &relay.last_finalized_block_header; + Self::verify_ancestry(ancestry_proof, last_header.hash(), &header)?; + + if let Err(e) = self.finality.validate( + header.clone(), + &validator_set, + validator_set_id, + justifications, + relay, + ) { + match e { + Error::NoJustificationFound => return Ok(()), + _ => return Err(e), + } + } + + // A valid grandpa proof proves finalization of all previous unjustified blocks. + relay.header_hashes.append(&mut relay.unjustified_headers); + relay.header_hashes.push(header.hash()); + + relay.set_last_finalized_block_header(header); + + if validator_set_id > relay.current_validator_set_id { + relay.current_validator_set = validator_set; + relay.current_validator_set_id = validator_set_id; + } + + Ok(()) + } + + fn submit_xt_to_be_included( + &mut self, + relay_id: RelayId, + extrinsic: OpaqueExtrinsic, + ) -> Result<(), Error> { + let relay = self + .light_validation_state + .tracked_relays + .get_mut(&relay_id) + .ok_or(Error::NoSuchRelayExists)?; + relay.verify_tx_inclusion.push(extrinsic); + + debug!( + "{} extrinsics in cache, waiting for inclusion verification", + relay.verify_tx_inclusion.len() + ); + + Ok(()) + } +} + +impl Validator for LightValidation +where + NumberFor: finality_grandpa::BlockNumberOps, + Block: ParentchainBlockTrait, + OCallApi: EnclaveOnChainOCallApi, +{ + fn initialize_grandpa_relay( + &mut self, + block_header: Block::Header, + validator_set: AuthorityList, + validator_set_proof: StorageProof, + ) -> Result { + let state_root = block_header.state_root(); + Self::check_validator_set_proof(state_root, validator_set_proof, &validator_set)?; + + self.initialize_relay(block_header, validator_set) + } + + fn initialize_parachain_relay( + &mut self, + block_header: Block::Header, + validator_set: AuthorityList, + ) -> Result { + self.initialize_relay(block_header, validator_set) + } + + fn submit_block( + &mut self, + relay_id: RelayId, + signed_block: &SignedBlock, + ) -> Result<(), Error> { + let header = signed_block.block.header(); + let justifications = signed_block.justifications.clone(); + + let relay = self + .light_validation_state + .tracked_relays + .get_mut(&relay_id) + .ok_or(Error::NoSuchRelayExists)?; + + if relay.last_finalized_block_header.hash() != *header.parent_hash() { + return Err(Error::HeaderAncestryMismatch) + } + let ancestry_proof = vec![]; + + self.submit_finalized_headers(relay_id, header.clone(), ancestry_proof, justifications) + } + + fn check_xt_inclusion(&mut self, relay_id: RelayId, block: &Block) -> Result<(), Error> { + let relay = self + .light_validation_state + .tracked_relays + .get_mut(&relay_id) + .ok_or(Error::NoSuchRelayExists)?; + + if relay.verify_tx_inclusion.is_empty() { + return Ok(()) + } + + let mut found_xts = vec![]; + block.extrinsics().iter().for_each(|xt| { + if let Some(index) = relay.verify_tx_inclusion.iter().position(|xt_opaque| { + >::hash_of(xt) == >::hash_of(xt_opaque) + }) { + found_xts.push(index); + } + }); + + // sort highest index first + found_xts.sort_by(|a, b| b.cmp(a)); + + let rm: Vec = + found_xts.into_iter().map(|i| relay.verify_tx_inclusion.remove(i)).collect(); + + if !rm.is_empty() { + info!("Verified inclusion proof of {} extrinsics.", rm.len()); + } + debug!( + "{} extrinsics remaining in cache, waiting for inclusion verification", + relay.verify_tx_inclusion.len() + ); + + Ok(()) + } + + fn set_state(&mut self, state: LightValidationState) { + self.light_validation_state = state; + } + + fn get_state(&self) -> &LightValidationState { + &self.light_validation_state + } +} + +impl ExtrinsicSender for LightValidation +where + NumberFor: finality_grandpa::BlockNumberOps, + Block: ParentchainBlockTrait, + OCallApi: EnclaveOnChainOCallApi, +{ + fn send_extrinsics(&mut self, extrinsics: Vec) -> Result<(), Error> { + for xt in extrinsics.iter() { + self.submit_xt_to_be_included(self.num_relays(), xt.clone()).expect("No Relays"); + } + + self.ocall_api + .send_to_parentchain(extrinsics) + .map_err(|e| Error::Other(format!("Failed to send extrinsics: {}", e).into())) + } +} + +impl LightClientState for LightValidation +where + NumberFor: finality_grandpa::BlockNumberOps, + Block: ParentchainBlockTrait, + OCallApi: EnclaveOnChainOCallApi, +{ + fn num_xt_to_be_included(&self, relay_id: RelayId) -> Result { + let relay = self + .light_validation_state + .tracked_relays + .get(&relay_id) + .ok_or(Error::NoSuchRelayExists)?; + Ok(relay.verify_tx_inclusion.len()) + } + + fn genesis_hash(&self, relay_id: RelayId) -> Result, Error> { + let relay = self + .light_validation_state + .tracked_relays + .get(&relay_id) + .ok_or(Error::NoSuchRelayExists)?; + Ok(relay.header_hashes[0]) + } + + fn latest_finalized_header(&self, relay_id: RelayId) -> Result { + let relay = self + .light_validation_state + .tracked_relays + .get(&relay_id) + .ok_or(Error::NoSuchRelayExists)?; + Ok(relay.last_finalized_block_header.clone()) + } + + fn penultimate_finalized_block_header( + &self, + relay_id: RelayId, + ) -> Result { + let relay = self + .light_validation_state + .tracked_relays + .get(&relay_id) + .ok_or(Error::NoSuchRelayExists)?; + Ok(relay.penultimate_finalized_block_header.clone()) + } + + fn num_relays(&self) -> RelayId { + self.light_validation_state.num_relays + } +} + +impl fmt::Debug for LightValidation +where + NumberFor: finality_grandpa::BlockNumberOps, + Block: ParentchainBlockTrait, + OCallApi: EnclaveOnChainOCallApi, +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "LightValidation {{ num_relays: {}, tracked_relays: {:?} }}", + self.light_validation_state.num_relays, self.light_validation_state.tracked_relays + ) + } +} diff --git a/tee-worker/core/parentchain/light-client/src/light_validation_state.rs b/tee-worker/core/parentchain/light-client/src/light_validation_state.rs new file mode 100644 index 0000000000..74d2691d25 --- /dev/null +++ b/tee-worker/core/parentchain/light-client/src/light_validation_state.rs @@ -0,0 +1,42 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +//! State of the light-client validation. + +use crate::{state::RelayState, RelayId}; +use codec::{Decode, Encode}; +pub use sp_finality_grandpa::SetId; +use sp_runtime::traits::Block as ParentchainBlockTrait; +use std::collections::BTreeMap; + +#[derive(Encode, Decode, Clone, Debug)] +pub struct LightValidationState { + pub num_relays: RelayId, + pub tracked_relays: BTreeMap>, +} + +impl LightValidationState { + pub fn new() -> Self { + Self { num_relays: Default::default(), tracked_relays: Default::default() } + } +} + +impl Default for LightValidationState { + fn default() -> Self { + Self { num_relays: Default::default(), tracked_relays: Default::default() } + } +} diff --git a/tee-worker/core/parentchain/light-client/src/mocks/mod.rs b/tee-worker/core/parentchain/light-client/src/mocks/mod.rs new file mode 100644 index 0000000000..4dedae8c6d --- /dev/null +++ b/tee-worker/core/parentchain/light-client/src/mocks/mod.rs @@ -0,0 +1,20 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +pub mod validator_access_mock; +pub mod validator_mock; +pub mod validator_mock_seal; diff --git a/tee-worker/core/parentchain/light-client/src/mocks/validator_access_mock.rs b/tee-worker/core/parentchain/light-client/src/mocks/validator_access_mock.rs new file mode 100644 index 0000000000..7a182d130e --- /dev/null +++ b/tee-worker/core/parentchain/light-client/src/mocks/validator_access_mock.rs @@ -0,0 +1,57 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +#[cfg(feature = "sgx")] +use std::sync::SgxRwLock as RwLock; + +#[cfg(feature = "std")] +use std::sync::RwLock; + +use crate::{ + concurrent_access::ValidatorAccess, + error::{Error, Result}, + mocks::validator_mock::ValidatorMock, +}; +use itp_types::Block; + +/// Mock for the validator access. +/// +/// Does not execute anything, just a stub. +#[derive(Default)] +pub struct ValidatorAccessMock { + validator: RwLock, +} + +impl ValidatorAccess for ValidatorAccessMock { + type ValidatorType = ValidatorMock; + + fn execute_on_validator(&self, getter_function: F) -> Result + where + F: FnOnce(&Self::ValidatorType) -> Result, + { + let validator_lock = self.validator.read().map_err(|_| Error::PoisonedLock)?; + getter_function(&validator_lock) + } + + fn execute_mut_on_validator(&self, mutating_function: F) -> Result + where + F: FnOnce(&mut Self::ValidatorType) -> Result, + { + let mut validator_lock = self.validator.write().map_err(|_| Error::PoisonedLock)?; + mutating_function(&mut validator_lock) + } +} diff --git a/tee-worker/core/parentchain/light-client/src/mocks/validator_mock.rs b/tee-worker/core/parentchain/light-client/src/mocks/validator_mock.rs new file mode 100644 index 0000000000..620a429177 --- /dev/null +++ b/tee-worker/core/parentchain/light-client/src/mocks/validator_mock.rs @@ -0,0 +1,101 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +use crate::{ + error::Result, AuthorityList, ExtrinsicSender, HashFor, LightClientState, LightValidationState, + RelayId, Validator, +}; +use itc_parentchain_test::parentchain_header_builder::ParentchainHeaderBuilder; +use itp_storage::StorageProof; +use itp_types::Block; +use sp_runtime::{generic::SignedBlock, traits::Block as BlockT, OpaqueExtrinsic}; +use std::vec::Vec; + +type Header = ::Header; + +/// Validator mock to be used in tests. +#[derive(Clone, Debug, Default)] +pub struct ValidatorMock { + light_validation_state: LightValidationState, +} + +impl Validator for ValidatorMock { + fn initialize_grandpa_relay( + &mut self, + _block_header: Header, + _validator_set: AuthorityList, + _validator_set_proof: StorageProof, + ) -> Result { + todo!() + } + + fn initialize_parachain_relay( + &mut self, + _block_header: Header, + _validator_set: AuthorityList, + ) -> Result { + todo!() + } + + fn submit_block( + &mut self, + _relay_id: RelayId, + _signed_block: &SignedBlock, + ) -> Result<()> { + Ok(()) + } + + fn check_xt_inclusion(&mut self, _relay_id: RelayId, _block: &Block) -> Result<()> { + Ok(()) + } + + fn set_state(&mut self, state: LightValidationState) { + self.light_validation_state = state; + } + + fn get_state(&self) -> &LightValidationState { + &self.light_validation_state + } +} + +impl ExtrinsicSender for ValidatorMock { + fn send_extrinsics(&mut self, _extrinsics: Vec) -> Result<()> { + Ok(()) + } +} + +impl LightClientState for ValidatorMock { + fn num_xt_to_be_included(&self, _relay_id: RelayId) -> Result { + todo!() + } + + fn genesis_hash(&self, _relay_id: RelayId) -> Result> { + todo!() + } + + fn latest_finalized_header(&self, _relay_id: RelayId) -> Result
{ + Ok(ParentchainHeaderBuilder::default().build()) + } + + fn penultimate_finalized_block_header(&self, _relay_id: RelayId) -> Result
{ + Ok(ParentchainHeaderBuilder::default().build()) + } + + fn num_relays(&self) -> RelayId { + 0 + } +} diff --git a/tee-worker/core/parentchain/light-client/src/mocks/validator_mock_seal.rs b/tee-worker/core/parentchain/light-client/src/mocks/validator_mock_seal.rs new file mode 100644 index 0000000000..6248c7c87d --- /dev/null +++ b/tee-worker/core/parentchain/light-client/src/mocks/validator_mock_seal.rs @@ -0,0 +1,37 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +use crate::{error::Error, LightValidationState}; +use itp_sgx_io::StaticSealedIO; +use itp_types::Block; + +/// A seal that returns a mock validator. +#[derive(Clone)] +pub struct LightValidationStateSealMock; + +impl StaticSealedIO for LightValidationStateSealMock { + type Error = Error; + type Unsealed = LightValidationState; + + fn unseal_from_static_file() -> Result { + Ok(LightValidationState::new()) + } + + fn seal_to_static_file(_unsealed: &Self::Unsealed) -> Result<(), Self::Error> { + Ok(()) + } +} diff --git a/tee-worker/core/parentchain/light-client/src/state.rs b/tee-worker/core/parentchain/light-client/src/state.rs new file mode 100644 index 0000000000..39d9d44471 --- /dev/null +++ b/tee-worker/core/parentchain/light-client/src/state.rs @@ -0,0 +1,77 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +use codec::{Decode, Encode}; +use sp_finality_grandpa::{AuthorityList, SetId}; +use sp_runtime::{ + traits::{Block as BlockT, Header as HeaderT}, + OpaqueExtrinsic, +}; +use std::{fmt, vec::Vec}; + +#[derive(Encode, Decode, Clone, PartialEq)] +pub struct RelayState { + pub last_finalized_block_header: Block::Header, + pub penultimate_finalized_block_header: Block::Header, + pub current_validator_set: AuthorityList, + pub current_validator_set_id: SetId, + pub header_hashes: Vec, + pub unjustified_headers: Vec, // Finalized headers without grandpa proof + pub verify_tx_inclusion: Vec, // Transactions sent by the relay + pub scheduled_change: Option>, // Scheduled Authorities change as indicated in the header's digest. +} + +#[derive(Encode, Decode, Clone, PartialEq)] +pub struct ScheduledChangeAtBlock { + pub at_block: Header::Number, + pub next_authority_list: AuthorityList, +} + +impl RelayState { + pub fn new(block_header: Block::Header, validator_set: AuthorityList) -> Self { + RelayState { + header_hashes: vec![block_header.hash()], + last_finalized_block_header: block_header.clone(), + // is it bad to initialize with the same? Header trait does no implement default... + penultimate_finalized_block_header: block_header, + current_validator_set: validator_set, + current_validator_set_id: 0, + unjustified_headers: Vec::new(), + verify_tx_inclusion: Vec::new(), + scheduled_change: None, + } + } + + pub fn set_last_finalized_block_header(&mut self, header: Block::Header) { + self.penultimate_finalized_block_header = self.last_finalized_block_header.clone(); + self.last_finalized_block_header = header; + } +} + +impl fmt::Debug for RelayState { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "RelayInfo {{ last_finalized_block_header_number: {:?}, current_validator_set: {:?}, \ + current_validator_set_id: {} amount of transaction in tx_inclusion_queue: {} }}", + self.last_finalized_block_header.number(), + self.current_validator_set, + self.current_validator_set_id, + self.verify_tx_inclusion.len() + ) + } +} diff --git a/tee-worker/core/parentchain/parentchain-crate/Cargo.toml b/tee-worker/core/parentchain/parentchain-crate/Cargo.toml new file mode 100644 index 0000000000..f9b361266e --- /dev/null +++ b/tee-worker/core/parentchain/parentchain-crate/Cargo.toml @@ -0,0 +1,40 @@ +[package] +authors = ["Integritee AG "] +edition = "2021" +name = "itc-parentchain" +version = "0.9.0" + +[dependencies] +codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive", "chain-error"] } + +# Parity +sp-runtime = { default-features = false, git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.29" } + +# local +itc-parentchain-block-import-dispatcher = { path = "../block-import-dispatcher", default-features = false } +itc-parentchain-block-importer = { path = "../block-importer", default-features = false } +itc-parentchain-indirect-calls-executor = { path = "../indirect-calls-executor", default-features = false } +itc-parentchain-light-client = { path = "../light-client", default-features = false } +itp-types = { default-features = false, path = "../../../core-primitives/types" } + +[features] +default = ["std"] +mocks = [ + "itc-parentchain-block-import-dispatcher/mocks", + "itc-parentchain-light-client/mocks", +] +sgx = [ + "itc-parentchain-block-import-dispatcher/sgx", + "itc-parentchain-block-importer/sgx", + "itc-parentchain-indirect-calls-executor/sgx", + "itc-parentchain-light-client/sgx", +] +std = [ + "codec/std", + "sp-runtime/std", + "itc-parentchain-block-import-dispatcher/std", + "itc-parentchain-block-importer/std", + "itc-parentchain-indirect-calls-executor/std", + "itc-parentchain-light-client/std", + "itp-types/std", +] diff --git a/tee-worker/core/parentchain/parentchain-crate/src/lib.rs b/tee-worker/core/parentchain/parentchain-crate/src/lib.rs new file mode 100644 index 0000000000..368ee69967 --- /dev/null +++ b/tee-worker/core/parentchain/parentchain-crate/src/lib.rs @@ -0,0 +1,33 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +//! Reexport all the parentchain components in one crate + +#![cfg_attr(not(feature = "std"), no_std)] + +#[cfg(all(feature = "std", feature = "sgx"))] +compile_error!("feature \"std\" and feature \"sgx\" cannot be enabled at the same time"); + +pub use itc_parentchain_block_import_dispatcher as block_import_dispatcher; + +pub use itc_parentchain_block_importer as block_importer; + +pub use itc_parentchain_indirect_calls_executor as indirect_calls_executor; + +pub use itc_parentchain_light_client as light_client; + +pub mod primitives; diff --git a/tee-worker/core/parentchain/parentchain-crate/src/primitives.rs b/tee-worker/core/parentchain/parentchain-crate/src/primitives.rs new file mode 100644 index 0000000000..a199a10c45 --- /dev/null +++ b/tee-worker/core/parentchain/parentchain-crate/src/primitives.rs @@ -0,0 +1,50 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +extern crate alloc; + +use crate::light_client::light_client_init_params::{GrandpaParams, SimpleParams}; +use codec::{Decode, Encode}; + +use sp_runtime::traits::Block; + +pub use itp_types::{Block as ParachainBlock, Block as SolochainBlock}; +pub type HeaderFor = ::Header; +pub type SolochainHeader = HeaderFor; +pub type ParachainHeader = HeaderFor; +pub type SolochainParams = GrandpaParams; +pub type ParachainParams = SimpleParams; + +/// Initialization primitives, used by both service and enclave. +/// Allows to use a single E-call for the initialization of different parentchain types. +#[derive(Encode, Decode, Clone)] +pub enum ParentchainInitParams { + Solochain { params: SolochainParams }, + Parachain { params: ParachainParams }, +} + +impl From for ParentchainInitParams { + fn from(params: SolochainParams) -> Self { + ParentchainInitParams::Solochain { params } + } +} + +impl From for ParentchainInitParams { + fn from(params: ParachainParams) -> Self { + ParentchainInitParams::Parachain { params } + } +} diff --git a/tee-worker/core/parentchain/test/Cargo.toml b/tee-worker/core/parentchain/test/Cargo.toml new file mode 100644 index 0000000000..872b982213 --- /dev/null +++ b/tee-worker/core/parentchain/test/Cargo.toml @@ -0,0 +1,40 @@ +[package] +authors = ["Integritee AG "] +edition = "2021" +homepage = "https://integritee.network/" +license = "Apache-2.0" +name = "itc-parentchain-test" +repository = "https://github.com/integritee-network/pallets/" +version = "0.9.0" + +[dependencies] +codec = { version = "3.0.0", default-features = false, features = ["derive"], package = "parity-scale-codec" } +itp-types = { path = "../../../core-primitives/types", default-features = false } +log = { version = "0.4.14", default-features = false } +scale-info = { version = "2.0.1", default-features = false, features = ["derive"] } +serde = { version = "1.0.13", features = ["derive"], optional = true } + +# substrate dependencies +frame-support = { default-features = false, git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.29" } +frame-system = { default-features = false, package = "frame-system", git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.29" } +sp-core = { default-features = false, git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.29" } +sp-io = { default-features = false, git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.29" } +sp-runtime = { default-features = false, git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.29" } +sp-std = { default-features = false, git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.29" } + +[features] +default = ["std"] +std = [ + "codec/std", + "log/std", + "scale-info/std", + "serde", + "itp-types/std", + # substrate dependencies + "frame-support/std", + "frame-system/std", + "sp-core/std", + "sp-io/std", + "sp-runtime/std", + "sp-std/std", +] diff --git a/tee-worker/core/parentchain/test/src/lib.rs b/tee-worker/core/parentchain/test/src/lib.rs new file mode 100644 index 0000000000..13ea29b756 --- /dev/null +++ b/tee-worker/core/parentchain/test/src/lib.rs @@ -0,0 +1,24 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + Copyright (C) 2017-2019 Baidu, Inc. All Rights Reserved. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +//! Builder patterns for common structs used in tests. + +#![cfg_attr(not(feature = "std"), no_std)] + +pub mod parentchain_block_builder; +pub mod parentchain_header_builder; diff --git a/tee-worker/core/parentchain/test/src/parentchain_block_builder.rs b/tee-worker/core/parentchain/test/src/parentchain_block_builder.rs new file mode 100644 index 0000000000..9d1c12c49e --- /dev/null +++ b/tee-worker/core/parentchain/test/src/parentchain_block_builder.rs @@ -0,0 +1,60 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + Copyright (C) 2017-2019 Baidu, Inc. All Rights Reserved. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +//! Builder pattern for a parentchain block. + +extern crate alloc; + +use crate::parentchain_header_builder::ParentchainHeaderBuilder; +use alloc::vec::Vec; +use itp_types::{Block, Header, SignedBlock}; +use sp_runtime::OpaqueExtrinsic; + +pub struct ParentchainBlockBuilder { + header: Header, + extrinsics: Vec, +} + +impl Default for ParentchainBlockBuilder { + fn default() -> Self { + ParentchainBlockBuilder { + header: ParentchainHeaderBuilder::default().build(), + extrinsics: Default::default(), + } + } +} + +impl ParentchainBlockBuilder { + pub fn with_header(mut self, header: Header) -> Self { + self.header = header; + self + } + + pub fn with_extrinsics(mut self, extrinsics: Vec) -> Self { + self.extrinsics = extrinsics; + self + } + + pub fn build(self) -> Block { + Block { header: self.header, extrinsics: self.extrinsics } + } + + pub fn build_signed(self) -> SignedBlock { + SignedBlock { block: self.build(), justifications: None } + } +} diff --git a/tee-worker/core/parentchain/test/src/parentchain_header_builder.rs b/tee-worker/core/parentchain/test/src/parentchain_header_builder.rs new file mode 100644 index 0000000000..ce4398c059 --- /dev/null +++ b/tee-worker/core/parentchain/test/src/parentchain_header_builder.rs @@ -0,0 +1,53 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + Copyright (C) 2017-2019 Baidu, Inc. All Rights Reserved. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +//! Builder pattern for a parentchain header. + +use itp_types::{BlockNumber, Header, H256}; +use sp_runtime::generic::Digest; + +#[derive(Default)] +pub struct ParentchainHeaderBuilder { + number: BlockNumber, + parent_hash: H256, + state_root: H256, + extrinsic_root: H256, + digest: Digest, +} + +impl ParentchainHeaderBuilder { + pub fn with_number(mut self, number: BlockNumber) -> Self { + self.number = number; + self + } + + pub fn with_parent_hash(mut self, parent_hash: H256) -> Self { + self.parent_hash = parent_hash; + self + } + + pub fn build(self) -> Header { + Header { + number: self.number, + parent_hash: self.parent_hash, + state_root: self.state_root, + extrinsics_root: self.extrinsic_root, + digest: self.digest, + } + } +} diff --git a/tee-worker/core/rest-client/Cargo.toml b/tee-worker/core/rest-client/Cargo.toml new file mode 100644 index 0000000000..21679f55d2 --- /dev/null +++ b/tee-worker/core/rest-client/Cargo.toml @@ -0,0 +1,51 @@ +[package] +authors = ["Integritee AG "] +edition = "2021" +name = "itc-rest-client" +version = "0.9.0" + +[dependencies] +# std dependencies +http = { version = "0.2", optional = true } +http_req = { optional = true, features = ["rust-tls"], branch = "master", git = "https://github.com/integritee-network/http_req" } +thiserror = { version = "1.0.26", optional = true } +url = { version = "2.0.0", optional = true } + +# sgx dependencies +http-sgx = { package = "http", git = "https://github.com/integritee-network/http-sgx.git", branch = "sgx-experimental", optional = true } +http_req-sgx = { optional = true, default-features = false, features = ["rust-tls", "sgx"], package = "http_req", git = "https://github.com/integritee-network/http_req" } +sgx_tstd = { branch = "master", git = "https://github.com/apache/teaclave-sgx-sdk.git", optional = true, features = ["net", "thread"] } +sgx_types = { branch = "master", git = "https://github.com/apache/teaclave-sgx-sdk.git", optional = true } +thiserror_sgx = { package = "thiserror", git = "https://github.com/mesalock-linux/thiserror-sgx", tag = "sgx_1.1.3", optional = true } +url_sgx = { package = "url", git = "https://github.com/mesalock-linux/rust-url-sgx", tag = "sgx_1.1.3", optional = true } + +# no_std dependencies +base64 = { version = "0.13", default-features = false, features = ["alloc"], optional = true } +base64_sgx = { package = "base64", rev = "sgx_1.1.3", git = "https://github.com/mesalock-linux/rust-base64-sgx", optional = true } +log = { version = "0.4", default-features = false } +serde = { version = "1.0", default-features = false, features = ["derive", "alloc"] } +serde_json = { version = "1.0", default-features = false, features = ["alloc"] } + +[features] +default = ["std"] +sgx = [ + "http-sgx", + "http_req-sgx", + "sgx_types", + "sgx_tstd", + "thiserror_sgx", + "url_sgx", + "base64_sgx", +] +std = [ + # std only + "http", + "http_req", + "thiserror", + "url", + # no_std + "base64/std", + "serde/std", + "serde_json/std", + "log/std", +] diff --git a/tee-worker/core/rest-client/src/error.rs b/tee-worker/core/rest-client/src/error.rs new file mode 100644 index 0000000000..8dea50ccfc --- /dev/null +++ b/tee-worker/core/rest-client/src/error.rs @@ -0,0 +1,58 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +#[cfg(all(not(feature = "std"), feature = "sgx"))] +use crate::sgx_reexport_prelude::*; + +use std::string::String; + +/// REST client error +#[derive(Debug, thiserror::Error)] +pub enum Error { + #[error("HTTP client creation failed")] + HttpClientError, + + #[error("Failed to parse final URL.")] + UrlError, + + #[error("Failed to serialize struct to JSON (in POST): {0}")] + SerializeParseError(serde_json::Error), + + #[error("Failed to deserialize data to struct (in GET or POST response: {0} {1}")] + DeserializeParseError(serde_json::Error, String), + + #[error("Failed to make the outgoing request")] + RequestError, + + #[error("HTTP header error: {0}")] + HttpHeaderError(http::header::ToStrError), + + #[error(transparent)] + HttpReqError(#[from] http_req::error::Error), + + #[error("Failed to perform IO operation: {0}")] + IoError(std::io::Error), + + #[error("Server returned non-success status: {0}, details: {1}")] + HttpError(u16, String), + + #[error("Request has timed out")] + TimeoutError, + + #[error("Invalid parameter value")] + InvalidValue, +} diff --git a/tee-worker/core/rest-client/src/fixtures/amazon_root_ca_1_v3.pem b/tee-worker/core/rest-client/src/fixtures/amazon_root_ca_1_v3.pem new file mode 100644 index 0000000000..a6f3e92af5 --- /dev/null +++ b/tee-worker/core/rest-client/src/fixtures/amazon_root_ca_1_v3.pem @@ -0,0 +1,20 @@ +-----BEGIN CERTIFICATE----- +MIIDQTCCAimgAwIBAgITBmyfz5m/jAo54vB4ikPmljZbyjANBgkqhkiG9w0BAQsF +ADA5MQswCQYDVQQGEwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6 +b24gUm9vdCBDQSAxMB4XDTE1MDUyNjAwMDAwMFoXDTM4MDExNzAwMDAwMFowOTEL +MAkGA1UEBhMCVVMxDzANBgNVBAoTBkFtYXpvbjEZMBcGA1UEAxMQQW1hem9uIFJv +b3QgQ0EgMTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALJ4gHHKeNXj +ca9HgFB0fW7Y14h29Jlo91ghYPl0hAEvrAIthtOgQ3pOsqTQNroBvo3bSMgHFzZM +9O6II8c+6zf1tRn4SWiw3te5djgdYZ6k/oI2peVKVuRF4fn9tBb6dNqcmzU5L/qw +IFAGbHrQgLKm+a/sRxmPUDgH3KKHOVj4utWp+UhnMJbulHheb4mjUcAwhmahRWa6 +VOujw5H5SNz/0egwLX0tdHA114gk957EWW67c4cX8jJGKLhD+rcdqsq08p8kDi1L +93FcXmn/6pUCyziKrlA4b9v7LWIbxcceVOF34GfID5yHI9Y/QCB/IIDEgEw+OyQm +jgSubJrIqg0CAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMC +AYYwHQYDVR0OBBYEFIQYzIU07LwMlJQuCFmcx7IQTgoIMA0GCSqGSIb3DQEBCwUA +A4IBAQCY8jdaQZChGsV2USggNiMOruYou6r4lK5IpDB/G/wkjUu0yKGX9rbxenDI +U5PMCCjjmCXPI6T53iHTfIUJrU6adTrCC2qJeHZERxhlbI1Bjjt/msv0tadQ1wUs +N+gDS63pYaACbvXy8MWy7Vu33PqUXHeeE6V/Uq2V8viTO96LXFvKWlJbYK8U90vv +o/ufQJVtMVT8QtPHRh8jrdkPSHCa2XV4cdFyQzR1bldZwgJcJmApzyMZFo6IQ6XU +5MsI+yMRQ+hDKXJioaldXgjUkK642M4UwtBV8ob2xJNDd2ZhwLnoQdeXeGADbkpy +rqXRfboQnoZsG4q5WTP468SQvvG5 +-----END CERTIFICATE----- diff --git a/tee-worker/core/rest-client/src/fixtures/baltimore_cyber_trust_root_v3.pem b/tee-worker/core/rest-client/src/fixtures/baltimore_cyber_trust_root_v3.pem new file mode 100644 index 0000000000..519028c63b --- /dev/null +++ b/tee-worker/core/rest-client/src/fixtures/baltimore_cyber_trust_root_v3.pem @@ -0,0 +1,21 @@ +-----BEGIN CERTIFICATE----- +MIIDdzCCAl+gAwIBAgIEAgAAuTANBgkqhkiG9w0BAQUFADBaMQswCQYDVQQGEwJJ +RTESMBAGA1UEChMJQmFsdGltb3JlMRMwEQYDVQQLEwpDeWJlclRydXN0MSIwIAYD +VQQDExlCYWx0aW1vcmUgQ3liZXJUcnVzdCBSb290MB4XDTAwMDUxMjE4NDYwMFoX +DTI1MDUxMjIzNTkwMFowWjELMAkGA1UEBhMCSUUxEjAQBgNVBAoTCUJhbHRpbW9y +ZTETMBEGA1UECxMKQ3liZXJUcnVzdDEiMCAGA1UEAxMZQmFsdGltb3JlIEN5YmVy +VHJ1c3QgUm9vdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKMEuyKr +mD1X6CZymrV51Cni4eiVgLGw41uOKymaZN+hXe2wCQVt2yguzmKiYv60iNoS6zjr +IZ3AQSsBUnuId9Mcj8e6uYi1agnnc+gRQKfRzMpijS3ljwumUNKoUMMo6vWrJYeK +mpYcqWe4PwzV9/lSEy/CG9VwcPCPwBLKBsua4dnKM3p31vjsufFoREJIE9LAwqSu +XmD+tqYF/LTdB1kC1FkYmGP1pWPgkAx9XbIGevOF6uvUA65ehD5f/xXtabz5OTZy +dc93Uk3zyZAsuT3lySNTPx8kmCFcB5kpvcY67Oduhjprl3RjM71oGDHweI12v/ye +jl0qhqdNkNwnGjkCAwEAAaNFMEMwHQYDVR0OBBYEFOWdWTCCR1jMrPoIVDaGezq1 +BE3wMBIGA1UdEwEB/wQIMAYBAf8CAQMwDgYDVR0PAQH/BAQDAgEGMA0GCSqGSIb3 +DQEBBQUAA4IBAQCFDF2O5G9RaEIFoN27TyclhAO992T9Ldcw46QQF+vaKSm2eT92 +9hkTI7gQCvlYpNRhcL0EYWoSihfVCr3FvDB81ukMJY2GQE/szKN+OMY3EU/t3Wgx +jkzSswF07r51XgdIGn9w/xZchMB5hbgF/X++ZRGjD8ACtPhSNzkE1akxehi/oCr0 +Epn3o0WC4zxe9Z2etciefC7IpJ5OCBRLbf1wbWsaY71k5h+3zvDyny67G7fyUIhz +ksLi4xaNmjICq44Y3ekQEe5+NauQrz4wlHrQMz2nZQ/1/I6eYs9HRCwBXbsdtTLS +R9I4LtD+gdwyah617jzV/OeBHRnDJELqYzmp +-----END CERTIFICATE----- diff --git a/tee-worker/core/rest-client/src/http_client.rs b/tee-worker/core/rest-client/src/http_client.rs new file mode 100644 index 0000000000..4bdd841edf --- /dev/null +++ b/tee-worker/core/rest-client/src/http_client.rs @@ -0,0 +1,570 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +#[cfg(all(not(feature = "std"), feature = "sgx"))] +use crate::sgx_reexport_prelude::*; + +#[cfg(all(not(feature = "std"), feature = "sgx"))] +use base64_sgx as base64; + +use crate::{error::Error, Query, RestPath}; +use http::{ + header::{HeaderName, AUTHORIZATION, CONTENT_LENGTH, CONTENT_TYPE, USER_AGENT}, + HeaderValue, +}; +use http_req::{ + request::{Method, Request}, + response::{Headers, Response}, + uri::Uri, +}; + +use log::*; +use std::{ + collections::HashMap, + convert::TryFrom, + str::FromStr, + string::{String, ToString}, + time::Duration, + vec::Vec, +}; +use url::Url; + +pub type EncodedBody = Vec; + +/// Simple trait to send HTTP request +pub trait SendHttpRequest { + fn send_request( + &self, + base_url: Url, + method: Method, + params: U, + query: Option<&Query<'_>>, + maybe_body: Option, + ) -> Result<(Response, EncodedBody), Error> + where + T: RestPath; +} + +/// Send trait used by the http client to send HTTP request, based on `http_req`. +pub trait Send { + fn execute_send_request( + &self, + request: &mut Request, + writer: &mut Vec, + ) -> Result; +} + +/// HTTP client implementation +/// +/// wrapper for the `http_req` library that adds the necessary headers and body to a request +pub struct HttpClient { + send: SendType, + send_null_body: bool, + timeout: Option, + headers: Headers, + authorization: Option, +} + +/// Default send method. +/// Automatically upgrades to TLS in case the base URL contains 'https' +/// For https requests, the default trusted server's certificates +/// are provided by the default tls configuration of the http_req lib +pub struct DefaultSend; + +impl Send for DefaultSend { + fn execute_send_request( + &self, + request: &mut Request, + writer: &mut Vec, + ) -> Result { + request.send(writer).map_err(Error::HttpReqError) + } +} + +/// Sends a HTTPs request with the server's root certificate. +/// The connection will only be established if the supplied certificate +/// matches the server's root certificate. +pub struct SendWithCertificateVerification { + root_certificate: String, +} + +impl SendWithCertificateVerification { + pub fn new(root_certificate: String) -> Self { + SendWithCertificateVerification { root_certificate } + } +} + +impl Send for SendWithCertificateVerification { + fn execute_send_request( + &self, + request: &mut Request, + writer: &mut Vec, + ) -> Result { + request + .send_with_pem_certificate(writer, Some(self.root_certificate.to_string())) + .map_err(Error::HttpReqError) + } +} + +impl HttpClient +where + SendType: Send, +{ + pub fn new( + send: SendType, + send_null_body: bool, + timeout: Option, + headers: Option, + authorization: Option, + ) -> Self { + HttpClient { + send, + send_null_body, + timeout, + headers: headers.unwrap_or_else(Headers::new), + authorization, + } + } + + /// Set credentials for HTTP Basic authentication. + pub fn set_auth(&mut self, user: &str, pass: &str) { + let mut s: String = user.to_string(); + s.push(':'); + s.push_str(pass); + self.authorization = Some(format!("Basic {}", base64::encode(&s))); + } + + /// Set HTTP header from string name and value. + /// + /// The header is added to all subsequent GET and POST requests + /// unless the headers are cleared with `clear_headers()` call. + pub fn set_header(&mut self, name: &'static str, value: &str) -> Result<(), Error> { + let header_name = HeaderName::from_str(name).map_err(|_| Error::InvalidValue)?; + let value = HeaderValue::from_str(value).map_err(|_| Error::InvalidValue)?; + + add_to_headers(&mut self.headers, header_name, value); + Ok(()) + } + + /// Clear all previously set headers + pub fn clear_headers(&mut self) { + self.headers = Headers::new(); + } +} + +impl SendHttpRequest for HttpClient +where + SendType: Send, +{ + fn send_request( + &self, + base_url: Url, + method: Method, + params: U, + query: Option<&Query<'_>>, + maybe_body: Option, + ) -> Result<(Response, EncodedBody), Error> + where + T: RestPath, + { + let url = join_url(base_url, T::get_path(params)?.as_str(), query)?; + let uri = Uri::try_from(url.as_str()).map_err(Error::HttpReqError)?; + + trace!("uri: {:?}", uri); + + let mut request = Request::new(&uri); + request.method(method); + + let mut request_headers = Headers::default_http(&uri); + + if let Some(body) = maybe_body.as_ref() { + if self.send_null_body || body != "null" { + let len = HeaderValue::from_str(&body.len().to_string()) + .map_err(|_| Error::RequestError)?; + + add_to_headers(&mut request_headers, CONTENT_LENGTH, len); + add_to_headers( + &mut request_headers, + CONTENT_TYPE, + HeaderValue::from_str("application/json") + .expect("Request Header: invalid characters"), + ); + + trace!("set request body: {}", body); + request.body(body.as_bytes()); // takes body non-owned (!) + } + } else { + debug!("no body to send"); + } + + if let Some(ref auth) = self.authorization { + add_to_headers( + &mut request_headers, + AUTHORIZATION, + HeaderValue::from_str(auth).map_err(|_| Error::RequestError)?, + ); + } + + // add pre-set headers + for (key, value) in self.headers.iter() { + request_headers.insert(key, &value.clone()); + } + + // add user agent header + let pkg_version = env!("CARGO_PKG_VERSION"); + add_to_headers( + &mut request_headers, + USER_AGENT, + HeaderValue::from_str(format!("integritee/{}", pkg_version).as_str()) + .map_err(|_| Error::RequestError)?, + ); + + request.headers(HashMap::from(request_headers)); + + request + .timeout(self.timeout) + .connect_timeout(self.timeout) + .read_timeout(self.timeout) + .write_timeout(self.timeout); + + trace!("{:?}", request); + + let mut writer = Vec::new(); + + let response = self.send.execute_send_request(&mut request, &mut writer)?; + + Ok((response, writer)) + } +} + +fn join_url(base_url: Url, path: &str, params: Option<&Query>) -> Result { + let mut url = base_url.join(path).map_err(|_| Error::UrlError)?; + + if let Some(params) = params { + for &(key, item) in params.iter() { + url.query_pairs_mut().append_pair(key, item); + } + } + + Ok(url) +} + +fn add_to_headers(headers: &mut Headers, key: HeaderName, value: HeaderValue) { + let header_value_str = value.to_str(); + + match header_value_str { + Ok(v) => { + headers.insert(key.as_str(), v); + }, + Err(e) => { + error!("Failed to add header to request: {:?}", e); + }, + } +} + +#[cfg(test)] +mod tests { + + use super::*; + use core::assert_matches::assert_matches; + use http::header::CONNECTION; + use serde::{Deserialize, Serialize}; + use std::vec::Vec; + + const HTTPBIN_ROOT_CERT: &str = include_str!("fixtures/amazon_root_ca_1_v3.pem"); + const COINGECKO_ROOT_CERTIFICATE: &str = + include_str!("fixtures/baltimore_cyber_trust_root_v3.pem"); + + #[test] + fn join_url_adds_query_parameters() { + let base_url = Url::parse("https://example.com").unwrap(); + let path = "api/v2/example_list"; + let query = [("filter", "all"), ("order", ("desc"))]; + + let complete_url = join_url(base_url, path, Some(&query)).unwrap(); + + assert_eq!( + complete_url.as_str(), + "https://example.com/api/v2/example_list?filter=all&order=desc" + ); + } + + #[test] + fn join_url_has_no_query_parameters() { + let base_url = Url::parse("https://example.com").unwrap(); + let path = "api/v2/endpoint"; + let complete_url = join_url(base_url, path, None).unwrap(); + assert_eq!(complete_url.as_str(), "https://example.com/api/v2/endpoint"); + } + + #[test] + fn join_url_with_too_many_slashes() { + let base_url = Url::parse("https://api.mydomain.com").unwrap(); + let path = "/api/v1/post"; + let complete_url = join_url(base_url, path, None).unwrap(); + assert_eq!(complete_url.as_str(), "https://api.mydomain.com/api/v1/post"); + } + + #[test] + #[ignore = "depends on external web-service that proved to be unreliable for CI"] + fn get_with_parameters() { + #[derive(Serialize, Deserialize, Debug)] + struct RequestArgs { + pub order: String, + pub filter: String, + } + + // Data structure that matches with REST API JSON + #[derive(Serialize, Deserialize, Debug)] + struct HttpBinAnything { + pub args: RequestArgs, + pub origin: String, + pub url: String, + } + + impl RestPath<()> for HttpBinAnything { + fn get_path(_: ()) -> Result { + Ok(format!("anything")) + } + } + + let http_client = HttpClient::new( + DefaultSend {}, + true, + Some(Duration::from_secs(3u64)), + Some(headers_connection_close()), + None, + ); + let base_url = Url::parse("https://httpbin.org").unwrap(); + let query_parameters = [("order", "desc"), ("filter", "all")]; + + let (response, encoded_body) = http_client + .send_request::<(), HttpBinAnything>( + base_url, + Method::GET, + (), + Some(&query_parameters), + None, + ) + .unwrap(); + + let response_body: HttpBinAnything = + deserialize_response_body(encoded_body.as_slice()).unwrap(); + + assert!(response.status_code().is_success()); + assert_eq!(response_body.args.order.as_str(), "desc"); + assert_eq!(response_body.args.filter.as_str(), "all"); + } + + #[test] + #[ignore = "depends on external web-service that proved to be unreliable for CI"] + fn get_without_parameters() { + // Data structure that matches with REST API JSON + #[derive(Serialize, Deserialize, Debug)] + struct HttpBinAnything { + pub method: String, + pub url: String, + } + + impl RestPath<()> for HttpBinAnything { + fn get_path(_: ()) -> Result { + Ok(format!("anything")) + } + } + + let http_client = HttpClient::new( + DefaultSend {}, + true, + Some(Duration::from_secs(3u64)), + Some(headers_connection_close()), + None, + ); + let base_url = Url::parse("https://httpbin.org").unwrap(); + + let (response, encoded_body) = http_client + .send_request::<(), HttpBinAnything>(base_url, Method::GET, (), None, None) + .unwrap(); + + let response_body: HttpBinAnything = + deserialize_response_body(encoded_body.as_slice()).unwrap(); + + assert!(response.status_code().is_success()); + assert!(!response_body.url.is_empty()); + assert_eq!(response_body.method.as_str(), "GET"); + } + + #[test] + #[ignore = "depends on external web-service that proved to be unreliable for CI"] + fn post_with_body() { + #[derive(Serialize, Deserialize, Debug)] + struct HttpBinAnything { + pub data: String, + pub method: String, + } + + impl RestPath<()> for HttpBinAnything { + fn get_path(_: ()) -> Result { + Ok(format!("anything")) + } + } + + let http_client = HttpClient::new( + DefaultSend {}, + false, + Some(Duration::from_secs(3u64)), + Some(headers_connection_close()), + None, + ); + + let body_test = "this is a test body with special characters {::}/-".to_string(); + let base_url = Url::parse("https://httpbin.org").unwrap(); + + let (response, encoded_body) = http_client + .send_request::<(), HttpBinAnything>( + base_url, + Method::POST, + (), + None, + Some(body_test.clone()), + ) + .unwrap(); + + let response_body: HttpBinAnything = + deserialize_response_body(encoded_body.as_slice()).unwrap(); + + assert!(response.status_code().is_success()); + assert_eq!(response_body.method.as_str(), "POST"); + assert_eq!(response_body.data, body_test); + } + + #[test] + #[ignore = "depends on external web-service that proved to be unreliable for CI"] + fn get_coins_list_from_coin_gecko_works() { + // Data structure that matches with REST API JSON + #[derive(Serialize, Deserialize, Debug)] + struct CoinGeckoCoinsList { + id: String, + symbol: String, + name: String, + } + + impl RestPath<()> for Vec { + fn get_path(_: ()) -> Result { + Ok(format!("api/v3/coins/list")) + } + } + + let http_client = + HttpClient::new(DefaultSend {}, true, Some(Duration::from_secs(3u64)), None, None); + let base_url = Url::parse("https://api.coingecko.com").unwrap(); + + let (response, encoded_body) = http_client + .send_request::<(), Vec>(base_url, Method::GET, (), None, None) + .unwrap(); + + let coins_list: Vec = + deserialize_response_body(encoded_body.as_slice()).unwrap(); + + assert!(response.status_code().is_success()); + assert!(!coins_list.is_empty()); + } + + #[test] + #[ignore = "depends on external web-service that proved to be unreliable for CI"] + fn authenticated_get_works() { + #[derive(Serialize, Deserialize, Debug)] + struct HttpBinAnything { + pub method: String, + pub url: String, + } + + impl RestPath<()> for HttpBinAnything { + fn get_path(_: ()) -> Result { + Ok(format!("anything")) + } + } + let base_url = Url::parse("https://httpbin.org").unwrap(); + let root_certificate = HTTPBIN_ROOT_CERT.to_string(); + + let http_client = HttpClient::new( + SendWithCertificateVerification { root_certificate }, + true, + Some(Duration::from_secs(3u64)), + Some(headers_connection_close()), + None, + ); + + let (response, encoded_body) = http_client + .send_request::<(), HttpBinAnything>(base_url, Method::GET, (), None, None) + .unwrap(); + + let response_body: HttpBinAnything = + deserialize_response_body(encoded_body.as_slice()).unwrap(); + + assert!(response.status_code().is_success()); + assert!(!response_body.url.is_empty()); + assert_eq!(response_body.method.as_str(), "GET"); + } + + #[test] + #[ignore = "depends on external web-service that proved to be unreliable for CI"] + fn authenticated_get_with_wrong_root_certificate_fails() { + #[derive(Serialize, Deserialize, Debug)] + struct HttpBinAnything { + pub method: String, + pub url: String, + } + + impl RestPath<()> for HttpBinAnything { + fn get_path(_: ()) -> Result { + Ok(format!("anything")) + } + } + + let base_url = Url::parse("https://httpbin.org").unwrap(); + let root_certificate = COINGECKO_ROOT_CERTIFICATE.to_string(); + + let http_client = HttpClient::new( + SendWithCertificateVerification { root_certificate }, + true, + Some(Duration::from_secs(3u64)), + Some(headers_connection_close()), + None, + ); + + let result = + http_client.send_request::<(), HttpBinAnything>(base_url, Method::GET, (), None, None); + assert_matches!(result, Err(Error::HttpReqError(_))); + let msg = format!("error {:?}", result.err()); + assert!(msg.contains("UnknownIssuer")); + } + + fn headers_connection_close() -> Headers { + let mut headers = Headers::new(); + add_to_headers(&mut headers, CONNECTION, HeaderValue::from_str("close").unwrap()); + headers + } + + fn deserialize_response_body<'a, T>(encoded_body: &'a [u8]) -> Result + where + T: Deserialize<'a>, + { + serde_json::from_slice::<'a, T>(encoded_body).map_err(|err| { + Error::DeserializeParseError(err, String::from_utf8_lossy(encoded_body).to_string()) + }) + } +} diff --git a/tee-worker/core/rest-client/src/http_client_builder.rs b/tee-worker/core/rest-client/src/http_client_builder.rs new file mode 100644 index 0000000000..1b51fc51a8 --- /dev/null +++ b/tee-worker/core/rest-client/src/http_client_builder.rs @@ -0,0 +1,112 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +#[cfg(all(not(feature = "std"), feature = "sgx"))] +use crate::sgx_reexport_prelude::*; + +use crate::{http_client, http_client::HttpClient}; +use http_req::response::Headers; +use std::{string::String, time::Duration}; + +/// Builder for `HttpClient` +pub struct HttpClientBuilder { + send: SendType, + + /// Request timeout + timeout: Duration, + + /// Send null body + send_null_body: bool, + + /// pre-set headers + headers: Option, + + /// authorization + authorization: Option, +} + +impl Default for HttpClientBuilder +where + SendType: Default, +{ + fn default() -> Self { + Self { + send: SendType::default(), + timeout: Duration::from_secs(u64::MAX), + send_null_body: true, + headers: None, + authorization: None, + } + } +} + +impl HttpClientBuilder +where + SendType: http_client::Send, +{ + /// Set send method. + /// + /// Default is calling the default send of http-req lib: all Mozilla's root certificates + /// are trusted. + pub fn send(mut self, send: SendType) -> Self { + self.send = send; + self + } + + /// Set request timeout + /// + /// Default is no timeout + pub fn timeout(mut self, timeout: Duration) -> Self { + self.timeout = timeout; + self + } + + /// Send null body in POST/PUT + /// + /// Default is yes + pub fn send_null_body(mut self, value: bool) -> Self { + self.send_null_body = value; + self + } + + /// Pre-set headers to attach to each request + /// + /// default is none + pub fn headers(mut self, headers: Headers) -> Self { + self.headers = Some(headers); + self + } + + /// Basic HTTP authorization (format: `username:password`) + /// + /// default is none + pub fn authorization(mut self, authorization: String) -> Self { + self.authorization = Some(authorization); + self + } + + /// Create `HttpClient` with the configuration in this builder + pub fn build(self) -> HttpClient { + HttpClient::::new( + self.send, + self.send_null_body, + Some(self.timeout), + self.headers, + self.authorization, + ) + } +} diff --git a/tee-worker/core/rest-client/src/lib.rs b/tee-worker/core/rest-client/src/lib.rs new file mode 100644 index 0000000000..8a397cefb8 --- /dev/null +++ b/tee-worker/core/rest-client/src/lib.rs @@ -0,0 +1,182 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +//! REST API Client, supporting SSL/TLS + +#![cfg_attr(not(feature = "std"), no_std)] +#![cfg_attr(test, feature(assert_matches))] + +#[cfg(all(feature = "std", feature = "sgx"))] +compile_error!("feature \"std\" and feature \"sgx\" cannot be enabled at the same time"); + +#[cfg(all(not(feature = "std"), feature = "sgx"))] +#[macro_use] +extern crate sgx_tstd as std; + +// re-export module to properly feature gate sgx and regular std environment +#[cfg(all(not(feature = "std"), feature = "sgx"))] +pub mod sgx_reexport_prelude { + pub use http_req_sgx as http_req; + pub use http_sgx as http; + pub use thiserror_sgx as thiserror; + pub use url_sgx as url; +} + +pub mod error; +pub mod http_client; +pub mod http_client_builder; +pub mod rest_client; + +#[cfg(test)] +pub mod mocks; + +use crate::error::Error; +use std::string::String; + +/// Type for URL query parameters. +/// +/// Slice of tuples in which the first field is parameter name and second is value. +/// These parameters are used with `get_with` and `post_with` functions. +/// +/// # Examples +/// The vector +/// ```ignore +/// vec![("param1", "1234"), ("param2", "abcd")] +/// ``` +/// would be parsed to **param1=1234¶m2=abcd** in the request URL. +pub type Query<'a> = [(&'a str, &'a str)]; + +/// Rest path builder trait for type. +/// +/// Provides implementation for `rest_path` function that builds +/// type (and REST endpoint) specific API path from given parameter(s). +/// The built REST path is appended to the base URL given to `RestClient`. +/// If `Err` is returned, it is propagated directly to API caller. +pub trait RestPath { + /// Construct type specific REST API path from given parameters + /// (e.g. "api/devices/1234"). + fn get_path(par: T) -> Result; +} + +/// REST HTTP GET trait +/// +/// Provides the GET verb for a REST API +pub trait RestGet { + /// Plain GET request + fn get(&mut self, params: U) -> Result + where + T: serde::de::DeserializeOwned + RestPath; + + /// GET request with query parameters. + fn get_with(&mut self, params: U, query: &Query<'_>) -> Result + where + T: serde::de::DeserializeOwned + RestPath; +} + +/// REST HTTP POST trait +/// +/// Provides the POST verb for a REST API +pub trait RestPost { + /// Plain POST request. + fn post(&mut self, params: U, data: &T) -> Result<(), Error> + where + T: serde::Serialize + RestPath; + + /// Make POST request with query parameters. + fn post_with(&mut self, params: U, data: &T, query: &Query<'_>) -> Result<(), Error> + where + T: serde::Serialize + RestPath; + + /// Make a POST request and capture returned body. + fn post_capture(&mut self, params: U, data: &T) -> Result + where + T: serde::Serialize + RestPath, + K: serde::de::DeserializeOwned; + + /// Make a POST request with query parameters and capture returned body. + fn post_capture_with( + &mut self, + params: U, + data: &T, + query: &Query<'_>, + ) -> Result + where + T: serde::Serialize + RestPath, + K: serde::de::DeserializeOwned; +} + +/// REST HTTP PUT trait +/// +/// Provides the PUT verb for a REST API +pub trait RestPut { + /// PUT request. + fn put(&mut self, params: U, data: &T) -> Result<(), Error> + where + T: serde::Serialize + RestPath; + + /// Make PUT request with query parameters. + fn put_with(&mut self, params: U, data: &T, query: &Query<'_>) -> Result<(), Error> + where + T: serde::Serialize + RestPath; + + /// Make a PUT request and capture returned body. + fn put_capture(&mut self, params: U, data: &T) -> Result + where + T: serde::Serialize + RestPath, + K: serde::de::DeserializeOwned; + + /// Make a PUT request with query parameters and capture returned body. + fn put_capture_with( + &mut self, + params: U, + data: &T, + query: &Query<'_>, + ) -> Result + where + T: serde::Serialize + RestPath, + K: serde::de::DeserializeOwned; +} + +/// REST HTTP PATCH trait +/// +/// Provides the PATCH verb for a REST API +pub trait RestPatch { + /// Make a PATCH request. + fn patch(&mut self, params: U, data: &T) -> Result<(), Error> + where + T: serde::Serialize + RestPath; + + /// Make PATCH request with query parameters. + fn patch_with(&mut self, params: U, data: &T, query: &Query<'_>) -> Result<(), Error> + where + T: serde::Serialize + RestPath; +} + +/// REST HTTP DELETE trait +/// +/// Provides the DELETE verb for a REST API +pub trait RestDelete { + /// Make a DELETE request. + fn delete(&mut self, params: U) -> Result<(), Error> + where + T: RestPath; + + /// Make a DELETE request with query and body. + fn delete_with(&mut self, params: U, data: &T, query: &Query<'_>) -> Result<(), Error> + where + T: serde::Serialize + RestPath; +} diff --git a/tee-worker/core/rest-client/src/mocks/http_client_mock.rs b/tee-worker/core/rest-client/src/mocks/http_client_mock.rs new file mode 100644 index 0000000000..454165ac39 --- /dev/null +++ b/tee-worker/core/rest-client/src/mocks/http_client_mock.rs @@ -0,0 +1,144 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +use crate::{ + error::Error, + http_client::{EncodedBody, SendHttpRequest}, + Query, RestPath, +}; +use http_req::{request::Method, response::Response}; +use serde::{Deserialize, Serialize}; +use url::Url; + +const DEFAULT_HEAD: &[u8; 102] = b"HTTP/1.1 200 OK\r\n\ + Date: Sat, 11 Jan 2003 02:44:04 GMT\r\n\ + Content-Type: text/html\r\n\ + Content-Length: 100\r\n\r\n"; + +/// Response body returned by the HTTP client mock, contains information passed in by caller +#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)] +pub struct ResponseBodyMock { + pub base_url: String, + pub method: String, + pub path: String, + pub request_body: Option, + pub query_parameters: Vec<(String, String)>, +} + +impl RestPath for ResponseBodyMock { + fn get_path(path: String) -> Result { + Ok(format!("{}", path)) + } +} + +/// HTTP client mock - to be used in unit tests +pub struct HttpClientMock { + response: Option, +} + +impl HttpClientMock { + pub fn new(response: Option) -> Self { + HttpClientMock { response } + } +} + +impl SendHttpRequest for HttpClientMock { + fn send_request( + &self, + base_url: Url, + method: Method, + params: U, + query: Option<&Query<'_>>, + maybe_body: Option, + ) -> Result<(Response, EncodedBody), Error> + where + T: RestPath, + { + let path = T::get_path(params)?; + let response = self + .response + .clone() + .unwrap_or_else(|| Response::from_head(DEFAULT_HEAD).unwrap()); + let base_url_str = String::from(base_url.as_str()); + + let query_parameters = query + .map(|q| q.iter().map(|(key, value)| (key.to_string(), value.to_string())).collect()) + .unwrap_or_else(|| Vec::<(String, String)>::new()); + + let response_body = ResponseBodyMock { + base_url: base_url_str, + method: format!("{:?}", method), + path, + request_body: maybe_body, + query_parameters, + }; + + let encoded_response_body = serde_json::to_vec(&response_body).unwrap(); + + Ok((response, encoded_response_body)) + } +} + +#[cfg(test)] +mod tests { + + use super::*; + + #[test] + pub fn response_body_mock_serialization_works() { + let response_body_mock = ResponseBodyMock { + base_url: "https://mydomain.com".to_string(), + method: "GET".to_string(), + path: "/api/v1".to_string(), + request_body: None, + query_parameters: vec![("order".to_string(), "desc".to_string())], + }; + + let serialized_body = serde_json::to_string(&response_body_mock).unwrap(); + let deserialized_body: ResponseBodyMock = + serde_json::from_str(serialized_body.as_str()).unwrap(); + + assert_eq!(deserialized_body, response_body_mock); + } + + #[test] + pub fn default_head_is_valid() { + assert!(Response::from_head(DEFAULT_HEAD).is_ok()); + } + + #[test] + pub fn client_mock_returns_parameters_in_result() { + let client_mock = HttpClientMock::new(None); + let base_url = Url::parse("https://integritee.network").unwrap(); + + let (response, encoded_response_body) = client_mock + .send_request::( + base_url, + Method::GET, + "/api/v1/get".to_string(), + None, + None, + ) + .unwrap(); + + let response_body: ResponseBodyMock = + serde_json::from_slice(encoded_response_body.as_slice()).unwrap(); + + assert_eq!(response, Response::from_head(DEFAULT_HEAD).unwrap()); + assert_eq!(response_body.method.as_str(), "GET"); + } +} diff --git a/tee-worker/core/rest-client/src/mocks/mod.rs b/tee-worker/core/rest-client/src/mocks/mod.rs new file mode 100644 index 0000000000..404a1b35d3 --- /dev/null +++ b/tee-worker/core/rest-client/src/mocks/mod.rs @@ -0,0 +1,18 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +pub mod http_client_mock; diff --git a/tee-worker/core/rest-client/src/rest_client.rs b/tee-worker/core/rest-client/src/rest_client.rs new file mode 100644 index 0000000000..36309331f4 --- /dev/null +++ b/tee-worker/core/rest-client/src/rest_client.rs @@ -0,0 +1,352 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +#[cfg(all(not(feature = "std"), feature = "sgx"))] +use crate::sgx_reexport_prelude::*; + +use crate::{ + error::Error, http_client::SendHttpRequest, Query, RestDelete, RestGet, RestPatch, RestPath, + RestPost, RestPut, +}; +use http_req::{request::Method, response::Headers}; +use log::*; +use std::string::{String, ToString}; +use url::Url; + +/// REST client to make HTTP GET and POST requests. +pub struct RestClient { + http_client: H, + baseurl: Url, + response_headers: Headers, + body_wash_fn: fn(String) -> String, +} + +impl RestClient +where + H: SendHttpRequest, +{ + /// Construct new client with default configuration to make HTTP requests. + /// + /// Use `Builder` to configure the client. + pub fn new(http_client: H, baseurl: Url) -> Self { + RestClient { + http_client, + baseurl, + response_headers: Headers::new(), + body_wash_fn: std::convert::identity, + } + } + + /// Set a function that cleans the response body up before deserializing it. + pub fn set_body_wash_fn(&mut self, func: fn(String) -> String) { + self.body_wash_fn = func; + } + + /// Response headers captured from previous request + pub fn response_headers(&mut self) -> &Headers { + &self.response_headers + } + + fn post_or_put(&mut self, method: Method, params: U, data: &T) -> Result<(), Error> + where + T: serde::Serialize + RestPath, + { + let data = serde_json::to_string(data).map_err(Error::SerializeParseError)?; + + let _body = self.make_request::(method, params, None, Some(data))?; + Ok(()) + } + + fn post_or_put_with( + &mut self, + method: Method, + params: U, + data: &T, + query: &Query<'_>, + ) -> Result<(), Error> + where + T: serde::Serialize + RestPath, + { + let data = serde_json::to_string(data).map_err(Error::SerializeParseError)?; + + let _body = self.make_request::(method, params, Some(query), Some(data))?; + Ok(()) + } + + fn post_or_put_capture( + &mut self, + method: Method, + params: U, + data: &T, + ) -> Result + where + T: serde::Serialize + RestPath, + K: serde::de::DeserializeOwned, + { + let data = serde_json::to_string(data).map_err(Error::SerializeParseError)?; + + let body = self.make_request::(method, params, None, Some(data))?; + serde_json::from_str(body.as_str()).map_err(|err| Error::DeserializeParseError(err, body)) + } + + fn post_or_put_capture_with( + &mut self, + method: Method, + params: U, + data: &T, + query: &Query<'_>, + ) -> Result + where + T: serde::Serialize + RestPath, + K: serde::de::DeserializeOwned, + { + let data = serde_json::to_string(data).map_err(Error::SerializeParseError)?; + + let body = self.make_request::(method, params, Some(query), Some(data))?; + serde_json::from_str(body.as_str()).map_err(|err| Error::DeserializeParseError(err, body)) + } + + fn make_request( + &mut self, + method: Method, + params: U, + query: Option<&Query<'_>>, + maybe_body: Option, + ) -> Result + where + T: RestPath, + { + let (response, encoded_body) = self.http_client.send_request::( + self.baseurl.clone(), + method, + params, + query, + maybe_body, + )?; + + self.response_headers = response.headers().clone(); + let status_code = response.status_code(); + + if !status_code.is_success() { + let status_code_num = u16::from(status_code); + let reason = String::from(status_code.reason().unwrap_or("none")); + return Err(Error::HttpError(status_code_num, reason)) + } + + let body = String::from_utf8_lossy(&encoded_body).to_string(); + + trace!("response headers: {:?}", self.response_headers); + trace!("response body: {}", body); + Ok((self.body_wash_fn)(body)) + } +} + +impl RestGet for RestClient +where + H: SendHttpRequest, +{ + /// Make a GET request. + fn get(&mut self, params: U) -> Result + where + T: serde::de::DeserializeOwned + RestPath, + { + let body = self.make_request::(Method::GET, params, None, None)?; + + serde_json::from_str(body.as_str()).map_err(|err| Error::DeserializeParseError(err, body)) + } + + /// Make a GET request with query parameters. + fn get_with(&mut self, params: U, query: &Query<'_>) -> Result + where + T: serde::de::DeserializeOwned + RestPath, + { + let body = self.make_request::(Method::GET, params, Some(query), None)?; + + serde_json::from_str(body.as_str()).map_err(|err| Error::DeserializeParseError(err, body)) + } +} + +impl RestPost for RestClient +where + H: SendHttpRequest, +{ + /// Make a POST request. + fn post(&mut self, params: U, data: &T) -> Result<(), Error> + where + T: serde::Serialize + RestPath, + { + self.post_or_put(Method::POST, params, data) + } + + /// Make POST request with query parameters. + fn post_with(&mut self, params: U, data: &T, query: &Query<'_>) -> Result<(), Error> + where + T: serde::Serialize + RestPath, + { + self.post_or_put_with(Method::POST, params, data, query) + } + + /// Make a POST request and capture returned body. + fn post_capture(&mut self, params: U, data: &T) -> Result + where + T: serde::Serialize + RestPath, + K: serde::de::DeserializeOwned, + { + self.post_or_put_capture(Method::POST, params, data) + } + + /// Make a POST request with query parameters and capture returned body. + fn post_capture_with( + &mut self, + params: U, + data: &T, + query: &Query<'_>, + ) -> Result + where + T: serde::Serialize + RestPath, + K: serde::de::DeserializeOwned, + { + self.post_or_put_capture_with(Method::POST, params, data, query) + } +} + +impl RestPut for RestClient +where + H: SendHttpRequest, +{ + /// Make a PUT request. + fn put(&mut self, params: U, data: &T) -> Result<(), Error> + where + T: serde::Serialize + RestPath, + { + self.post_or_put(Method::PUT, params, data) + } + + /// Make PUT request with query parameters. + fn put_with(&mut self, params: U, data: &T, query: &Query<'_>) -> Result<(), Error> + where + T: serde::Serialize + RestPath, + { + self.post_or_put_with(Method::PUT, params, data, query) + } + + /// Make a PUT request and capture returned body. + fn put_capture(&mut self, params: U, data: &T) -> Result + where + T: serde::Serialize + RestPath, + K: serde::de::DeserializeOwned, + { + self.post_or_put_capture(Method::PUT, params, data) + } + + /// Make a PUT request with query parameters and capture returned body. + fn put_capture_with( + &mut self, + params: U, + data: &T, + query: &Query<'_>, + ) -> Result + where + T: serde::Serialize + RestPath, + K: serde::de::DeserializeOwned, + { + self.post_or_put_capture_with(Method::PUT, params, data, query) + } +} + +impl RestPatch for RestClient +where + H: SendHttpRequest, +{ + /// Make a PATCH request. + fn patch(&mut self, params: U, data: &T) -> Result<(), Error> + where + T: serde::Serialize + RestPath, + { + self.post_or_put(Method::PATCH, params, data) + } + + /// Make PATCH request with query parameters. + fn patch_with(&mut self, params: U, data: &T, query: &Query<'_>) -> Result<(), Error> + where + T: serde::Serialize + RestPath, + { + self.post_or_put_with(Method::PATCH, params, data, query) + } +} + +impl RestDelete for RestClient +where + H: SendHttpRequest, +{ + /// Make a DELETE request. + fn delete(&mut self, params: U) -> Result<(), Error> + where + T: RestPath, + { + self.make_request::(Method::DELETE, params, None, None)?; + Ok(()) + } + + /// Make a DELETE request with query and body. + fn delete_with(&mut self, params: U, data: &T, query: &Query<'_>) -> Result<(), Error> + where + T: serde::Serialize + RestPath, + { + let data = serde_json::to_string(data).map_err(Error::SerializeParseError)?; + self.make_request::(Method::DELETE, params, Some(query), Some(data))?; + Ok(()) + } +} + +#[cfg(test)] +mod tests { + + use super::*; + use crate::mocks::http_client_mock::{HttpClientMock, ResponseBodyMock}; + + #[test] + pub fn get_sends_proper_request() { + let mut rest_client = create_default_rest_client(); + + let get_response = + rest_client.get::("/api/v2/get".to_string()).unwrap(); + + assert_eq!(get_response.method.as_str(), "GET"); + assert_eq!(get_response.path.as_str(), "/api/v2/get"); + } + + #[test] + pub fn get_with_query_parameters_works() { + let mut rest_client = create_default_rest_client(); + + let get_response = rest_client + .get_with::( + "/api/v1/get".to_string(), + &[("order", "desc"), ("user", "spongebob")], + ) + .unwrap(); + + assert_eq!(2, get_response.query_parameters.len()); + } + + fn create_default_rest_client() -> RestClient { + let base_url = Url::parse("https://example.com").unwrap(); + let http_client = HttpClientMock::new(None); + RestClient::new(http_client, base_url) + } +} diff --git a/tee-worker/core/rpc-client/Cargo.toml b/tee-worker/core/rpc-client/Cargo.toml new file mode 100644 index 0000000000..99c6000909 --- /dev/null +++ b/tee-worker/core/rpc-client/Cargo.toml @@ -0,0 +1,30 @@ +[package] +authors = ["Integritee AG "] +edition = "2021" +name = "itc-rpc-client" +version = "0.9.0" + +[dependencies] +# crates.io +codec = { package = "parity-scale-codec", version = "3.0.0", features = ["derive"] } +log = "0.4" +openssl = { version = "0.10" } +parking_lot = "0.12.1" +serde_derive = "1.0" +serde_json = "1.0" +sgx_crypto_helper = { branch = "master", git = "https://github.com/apache/teaclave-sgx-sdk.git" } +substrate-api-client = { git = "https://github.com/scs/substrate-api-client.git", branch = "polkadot-v0.9.29" } +thiserror = { version = "1.0" } +url = { version = "2.0.0" } +ws = { version = "0.9.1", features = ["ssl"] } + +# local +itp-rpc = { path = "../../core-primitives/rpc" } +itp-types = { path = "../../core-primitives/types" } +itp-utils = { path = "../../core-primitives/utils" } + +[dev-dependencies] +env_logger = "0.9.0" +itc-tls-websocket-server = { path = "../tls-websocket-server", features = ["mocks"] } +itp-networking-utils = { path = "../../core-primitives/networking-utils" } +rustls = { version = "0.19", features = ["dangerous_configuration"] } diff --git a/tee-worker/core/rpc-client/src/direct_client.rs b/tee-worker/core/rpc-client/src/direct_client.rs new file mode 100644 index 0000000000..1a761de286 --- /dev/null +++ b/tee-worker/core/rpc-client/src/direct_client.rs @@ -0,0 +1,270 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +//! Interface for direct access to a workers rpc. + +use crate::ws_client::{WsClient, WsClientControl}; +use codec::Decode; +use itp_rpc::{RpcRequest, RpcResponse, RpcReturnValue}; +use itp_types::DirectRequestStatus; +use itp_utils::FromHexPrefixed; +use log::*; +use sgx_crypto_helper::rsa3072::Rsa3072PubKey; +use std::{ + sync::{ + mpsc::{channel, Sender as MpscSender}, + Arc, + }, + thread, + thread::JoinHandle, +}; +use substrate_api_client::RuntimeMetadataPrefixed; + +pub use crate::error::{Error, Result}; + +#[derive(Clone)] +pub struct DirectClient { + url: String, + web_socket_control: Arc, +} +pub trait DirectApi { + /// Server connection with only one response. + fn get(&self, request: &str) -> Result; + /// Server connection with more than one response. + fn watch(&self, request: String, sender: MpscSender) -> JoinHandle<()>; + fn get_rsa_pubkey(&self) -> Result; + fn get_mu_ra_url(&self) -> Result; + fn get_untrusted_worker_url(&self) -> Result; + fn get_state_metadata(&self) -> Result; + + fn send(&self, request: &str) -> Result<()>; + /// Close any open websocket connection. + fn close(&self) -> Result<()>; +} + +impl DirectClient { + pub fn new(url: String) -> Self { + Self { url, web_socket_control: Default::default() } + } +} + +impl Drop for DirectClient { + fn drop(&mut self) { + if let Err(e) = self.close() { + error!("Failed to close web-socket connection: {:?}", e); + } + } +} + +impl DirectApi for DirectClient { + fn get(&self, request: &str) -> Result { + let (port_in, port_out) = channel(); + + info!("[WorkerApi Direct]: (get) Sending request: {:?}", request); + WsClient::connect_one_shot(&self.url, request, port_in)?; + debug!("Waiting for web-socket result.."); + port_out.recv().map_err(Error::MspcReceiver) + } + + fn watch(&self, request: String, sender: MpscSender) -> JoinHandle<()> { + info!("[WorkerApi Direct]: (watch) Sending request: {:?}", request); + let url = self.url.clone(); + + let web_socket_control = self.web_socket_control.clone(); + // Unwrap is fine here, because JoinHandle can be used to handle a Thread panic. + thread::spawn(move || { + WsClient::connect_watch_with_control(&url, &request, &sender, web_socket_control) + .expect("Connection failed") + }) + } + + fn get_rsa_pubkey(&self) -> Result { + let jsonrpc_call: String = RpcRequest::compose_jsonrpc_call( + "author_getShieldingKey".to_string(), + Default::default(), + )?; + + // Send json rpc call to ws server. + let response_str = self.get(&jsonrpc_call)?; + + let shielding_pubkey_string = decode_from_rpc_response(&response_str)?; + let shielding_pubkey: Rsa3072PubKey = serde_json::from_str(&shielding_pubkey_string)?; + + info!("[+] Got RSA public key of enclave"); + Ok(shielding_pubkey) + } + + fn get_mu_ra_url(&self) -> Result { + let jsonrpc_call: String = + RpcRequest::compose_jsonrpc_call("author_getMuRaUrl".to_string(), Default::default())?; + + // Send json rpc call to ws server. + let response_str = self.get(&jsonrpc_call)?; + + let mu_ra_url: String = decode_from_rpc_response(&response_str)?; + + info!("[+] Got mutual remote attestation url of enclave: {}", mu_ra_url); + Ok(mu_ra_url) + } + + fn get_untrusted_worker_url(&self) -> Result { + let jsonrpc_call: String = RpcRequest::compose_jsonrpc_call( + "author_getUntrustedUrl".to_string(), + Default::default(), + )?; + + // Send json rpc call to ws server. + let response_str = self.get(&jsonrpc_call)?; + + let untrusted_url: String = decode_from_rpc_response(&response_str)?; + + info!("[+] Got untrusted websocket url of worker: {}", untrusted_url); + Ok(untrusted_url) + } + + fn get_state_metadata(&self) -> Result { + let jsonrpc_call: String = + RpcRequest::compose_jsonrpc_call("state_getMetadata".to_string(), Default::default())?; + + // Send json rpc call to ws server. + let response_str = self.get(&jsonrpc_call)?; + + // Decode rpc response. + let rpc_response: RpcResponse = serde_json::from_str(&response_str)?; + let rpc_return_value = RpcReturnValue::from_hex(&rpc_response.result) + .map_err(|e| Error::Custom(Box::new(e)))?; + + // Decode Metadata. + let metadata = RuntimeMetadataPrefixed::decode(&mut rpc_return_value.value.as_slice())?; + + println!("[+] Got metadata of enclave runtime"); + Ok(metadata) + } + + fn send(&self, request: &str) -> Result<()> { + self.web_socket_control.send(request) + } + + fn close(&self) -> Result<()> { + self.web_socket_control.close_connection() + } +} + +fn decode_from_rpc_response(json_rpc_response: &str) -> Result { + let rpc_response: RpcResponse = serde_json::from_str(json_rpc_response)?; + let rpc_return_value = + RpcReturnValue::from_hex(&rpc_response.result).map_err(|e| Error::Custom(Box::new(e)))?; + let response_message = String::decode(&mut rpc_return_value.value.as_slice())?; + match rpc_return_value.status { + DirectRequestStatus::Ok => Ok(response_message), + _ => Err(Error::Status(response_message)), + } +} + +#[cfg(test)] +mod tests { + use super::*; + use itc_tls_websocket_server::{test::fixtures::test_server::create_server, WebSocketServer}; + use itp_networking_utils::ports::get_available_port_in_range; + use std::vec; + + #[test] + fn watch_works_and_closes_connection_on_demand() { + let _ = env_logger::builder().is_test(true).try_init(); + + const END_MESSAGE: &str = "End of service."; + let responses = vec![END_MESSAGE.to_string()]; + + let port = get_available_port_in_range(21000..21500).unwrap(); + let (server, handler) = create_server(responses, port); + + let server_clone = server.clone(); + let server_join_handle = thread::spawn(move || { + if let Err(e) = server_clone.run() { + error!("Web-socket server failed: {:?}", e); + } + }); + + // Wait until server is up. + while !server.is_running().unwrap() { + thread::sleep(std::time::Duration::from_millis(50)); + } + + let client = DirectClient::new(format!("wss://localhost:{}", port)); + let (message_sender, message_receiver) = channel::(); + + let client_join_handle = client.watch("Request".to_string(), message_sender); + + let mut messages = Vec::::new(); + loop { + info!("Client waiting to receive answer.. "); + let message = message_receiver.recv().unwrap(); + info!("Received answer: {}", message); + let do_close = message.as_str() == END_MESSAGE; + messages.push(message); + + if do_close { + info!("Client closing connection"); + break + } + } + + info!("Joining client thread"); + client.close().unwrap(); + client_join_handle.join().unwrap(); + + info!("Joining server thread"); + server.shut_down().unwrap(); + server_join_handle.join().unwrap(); + + assert_eq!(1, messages.len()); + assert_eq!(1, handler.messages_handled.read().unwrap().len()); + } + + #[test] + fn get_works_and_closes_connection() { + let _ = env_logger::builder().is_test(true).try_init(); + + let server_response = "response 1".to_string(); + let responses = vec![server_response.clone()]; + + let port = get_available_port_in_range(21501..22000).unwrap(); + let (server, handler) = create_server(responses, port); + + let server_clone = server.clone(); + let server_join_handle = thread::spawn(move || { + if let Err(e) = server_clone.run() { + error!("Web-socket server failed: {:?}", e); + } + }); + + // Wait until server is up. + while !server.is_running().unwrap() { + thread::sleep(std::time::Duration::from_millis(50)); + } + + let client = DirectClient::new(format!("wss://localhost:{}", port)); + let received_response = client.get("Request").unwrap(); + + info!("Joining server thread"); + server.shut_down().unwrap(); + server_join_handle.join().unwrap(); + + assert_eq!(server_response, received_response); + assert_eq!(1, handler.messages_handled.read().unwrap().len()); + } +} diff --git a/tee-worker/core/rpc-client/src/error.rs b/tee-worker/core/rpc-client/src/error.rs new file mode 100644 index 0000000000..5fc5a298da --- /dev/null +++ b/tee-worker/core/rpc-client/src/error.rs @@ -0,0 +1,39 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ +use codec::Error as CodecError; +use serde_json::Error as JsonError; +use std::{boxed::Box, sync::mpsc::RecvError}; +use thiserror; +use ws::Error as WsClientError; + +pub type Result = core::result::Result; + +#[derive(Debug, thiserror::Error)] +pub enum Error { + #[error("{0}")] + Codec(#[from] CodecError), + #[error("{0}")] + SerdeJson(#[from] JsonError), + #[error("Validateer returned the following error message: {0}")] + Status(String), + #[error("Websocket error: {0}")] + WsClientError(#[from] WsClientError), + #[error("Faulty channel: {0}")] + MspcReceiver(#[from] RecvError), + #[error("Custom Error: {0}")] + Custom(Box), +} diff --git a/tee-worker/core/rpc-client/src/lib.rs b/tee-worker/core/rpc-client/src/lib.rs new file mode 100644 index 0000000000..59c9949911 --- /dev/null +++ b/tee-worker/core/rpc-client/src/lib.rs @@ -0,0 +1,22 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +pub mod direct_client; +pub mod error; +#[cfg(test)] +pub mod mock; +pub mod ws_client; diff --git a/tee-worker/core/rpc-client/src/mock.rs b/tee-worker/core/rpc-client/src/mock.rs new file mode 100644 index 0000000000..dde7177f29 --- /dev/null +++ b/tee-worker/core/rpc-client/src/mock.rs @@ -0,0 +1,105 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +//! Interface for direct access to a workers rpc. + +use crate::{ + direct_client::{DirectApi, Error}, + error::Result, +}; +use codec::Decode; +use sgx_crypto_helper::rsa3072::Rsa3072PubKey; +use std::{sync::mpsc::Sender as MpscSender, thread::JoinHandle}; +use substrate_api_client::{FromHexString, RuntimeMetadataPrefixed}; + +#[derive(Clone, Default)] +pub struct DirectClientMock { + rsa_pubkey: Rsa3072PubKey, + mu_ra_url: String, + untrusted_worker_url: String, + metadata: String, +} + +impl DirectClientMock { + pub fn new( + rsa_pubkey: Rsa3072PubKey, + mu_ra_url: String, + untrusted_worker_url: String, + metadata: String, + ) -> Self { + Self { rsa_pubkey, mu_ra_url, untrusted_worker_url, metadata } + } + + pub fn with_rsa_pubkey(mut self, key: Rsa3072PubKey) -> Self { + self.rsa_pubkey = key; + self + } + + pub fn with_mu_ra_url(mut self, url: &str) -> Self { + self.mu_ra_url = url.to_string(); + self + } + + pub fn with_untrusted_worker_url(mut self, url: &str) -> Self { + self.untrusted_worker_url = url.to_string(); + self + } + + pub fn with_metadata(mut self, hex_metadata: String) -> Self { + self.metadata = hex_metadata; + self + } +} + +impl DirectApi for DirectClientMock { + fn get(&self, _request: &str) -> Result { + Ok("Hello_world".to_string()) + } + + fn watch(&self, _request: String, _sender: MpscSender) -> JoinHandle<()> { + unimplemented!() + } + + fn get_rsa_pubkey(&self) -> Result { + Ok(self.rsa_pubkey) + } + + fn get_mu_ra_url(&self) -> Result { + Ok(self.mu_ra_url.clone()) + } + + fn get_untrusted_worker_url(&self) -> Result { + Ok(self.untrusted_worker_url.clone()) + } + + fn get_state_metadata(&self) -> Result { + let metadata = match Vec::from_hex(self.metadata.clone()) { + Ok(m) => m, + Err(e) => + return Err(Error::Custom(format!("Decode metadata FromHexError: {:?}", e).into())), + }; + RuntimeMetadataPrefixed::decode(&mut metadata.as_slice()).map_err(|e| e.into()) + } + + fn send(&self, _request: &str) -> Result<()> { + unimplemented!() + } + + fn close(&self) -> Result<()> { + unimplemented!() + } +} diff --git a/tee-worker/core/rpc-client/src/ws_client.rs b/tee-worker/core/rpc-client/src/ws_client.rs new file mode 100644 index 0000000000..6e5b3d34b0 --- /dev/null +++ b/tee-worker/core/rpc-client/src/ws_client.rs @@ -0,0 +1,168 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +use crate::error::{Error, Result as RpcClientResult}; +///! Websocket client implementation to access the direct-rpc-server running inside an enclave. +/// +/// This should be replaced with the `jsonrpsee::WsClient`as soon as available in no-std: +/// https://github.com/paritytech/jsonrpsee/issues/1 +use log::*; +use openssl::ssl::{SslConnector, SslMethod, SslStream, SslVerifyMode}; +use parking_lot::Mutex; +use std::sync::{mpsc::Sender as MpscSender, Arc}; +use url::{self}; +use ws::{connect, util::TcpStream, CloseCode, Handler, Handshake, Message, Result, Sender}; + +/// Control a registered web-socket client. +#[derive(Default)] +pub struct WsClientControl { + subscriber: Mutex>, +} + +impl Clone for WsClientControl { + fn clone(&self) -> Self { + WsClientControl { subscriber: Mutex::new(self.subscriber.lock().clone()) } + } +} + +impl WsClientControl { + pub fn close_connection(&self) -> RpcClientResult<()> { + if let Some(s) = self.subscriber.lock().as_ref() { + debug!("Closing connection"); + s.close(CloseCode::Normal)?; + debug!("Connection is closed"); + } + Ok(()) + } + + fn subscribe_sender(&self, sender: Sender) -> RpcClientResult<()> { + let mut subscriber_lock = self.subscriber.lock(); + *subscriber_lock = Some(sender); + Ok(()) + } + + pub fn send(&self, request: &str) -> RpcClientResult<()> { + if let Some(s) = self.subscriber.lock().as_ref() { + s.send(request)?; + Ok(()) + } else { + Err(Error::Custom("Sender not initialized".into())) + } + } +} + +#[derive(Clone)] +pub struct WsClient { + web_socket: Sender, + request: String, + result: MpscSender, + do_watch: bool, +} + +impl WsClient { + /// Connect a web-socket client for multiple request/responses. + /// + /// Control over the connection is done using the provided client control. + /// (e.g. shutdown has to be initiated explicitly). + #[allow(clippy::result_large_err)] + pub fn connect_watch_with_control( + url: &str, + request: &str, + result: &MpscSender, + control: Arc, + ) -> Result<()> { + debug!("Connecting web-socket connection with watch"); + connect(url.to_string(), |out| { + control.subscribe_sender(out.clone()).expect("Failed sender subscription"); + WsClient::new(out, request.to_string(), result.clone(), true) + }) + } + + /// Connects a web-socket client for a one-shot request. + #[allow(clippy::result_large_err)] + pub fn connect_one_shot(url: &str, request: &str, result: MpscSender) -> Result<()> { + debug!("Connecting one-shot web-socket connection"); + connect(url.to_string(), |out| { + debug!("Create new web-socket client"); + WsClient::new(out, request.to_string(), result.clone(), false) + }) + } + + fn new( + web_socket: Sender, + request: String, + result: MpscSender, + do_watch: bool, + ) -> WsClient { + WsClient { web_socket, request, result, do_watch } + } +} + +impl Handler for WsClient { + fn on_open(&mut self, _: Handshake) -> Result<()> { + debug!("sending request: {:?}", self.request.clone()); + match self.web_socket.send(self.request.clone()) { + Ok(_) => Ok(()), + Err(e) => Err(e), + } + } + + fn on_message(&mut self, msg: Message) -> Result<()> { + trace!("got message"); + trace!("{}", msg); + trace!("sending result to MpscSender.."); + self.result.send(msg.to_string()).expect("Failed to send"); + if !self.do_watch { + debug!("do_watch is false, closing connection"); + self.web_socket.close(CloseCode::Normal).expect("Failed to close connection"); + debug!("Connection close requested"); + } + debug!("on_message successful, returning"); + Ok(()) + } + + fn on_close(&mut self, _code: CloseCode, _reason: &str) { + debug!("Web-socket close"); + self.web_socket.shutdown().expect("Failed to shutdown") + } + + /// we are overriding the `upgrade_ssl_client` method in order to disable hostname verification + /// this is taken from https://github.com/housleyjk/ws-rs/blob/master/examples/unsafe-ssl-client.rs + /// TODO: hostname verification should probably be enabled again for production? + fn upgrade_ssl_client( + &mut self, + sock: TcpStream, + _: &url::Url, + ) -> Result> { + let mut builder = SslConnector::builder(SslMethod::tls_client()).map_err(|e| { + ws::Error::new( + ws::ErrorKind::Internal, + format!("Failed to upgrade client to SSL: {}", e), + ) + })?; + builder.set_verify(SslVerifyMode::empty()); + + let connector = builder.build(); + connector + .configure() + .expect("Invalid connection config") + .use_server_name_indication(false) + .verify_hostname(false) + .connect("", sock) + .map_err(From::from) + } +} diff --git a/tee-worker/core/rpc-server/Cargo.toml b/tee-worker/core/rpc-server/Cargo.toml new file mode 100644 index 0000000000..62d819757a --- /dev/null +++ b/tee-worker/core/rpc-server/Cargo.toml @@ -0,0 +1,31 @@ +[package] +authors = ["Integritee AG "] +edition = "2021" +name = "itc-rpc-server" +version = "0.9.0" + +[dependencies] +anyhow = "1.0.40" +jsonrpsee = { version = "0.2.0-alpha.7", features = ["full"] } +log = "0.4" +parity-scale-codec = "3.0.0" +serde_json = "1.0.64" +tokio = { version = "1.6.1", features = ["full"] } + +# local +itp-enclave-api = { path = "../../core-primitives/enclave-api" } +itp-rpc = { path = "../../core-primitives/rpc" } +itp-utils = { path = "../../core-primitives/utils" } +its-peer-fetch = { path = "../../sidechain/peer-fetch" } +its-primitives = { path = "../../sidechain/primitives" } +its-rpc-handler = { path = "../../sidechain/rpc-handler" } +its-storage = { path = "../../sidechain/storage" } + +[features] +default = ["std"] +std = [] + +[dev-dependencies] +env_logger = { version = "*" } +its-test = { path = "../../sidechain/test" } +sp-core = { git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.29" } diff --git a/tee-worker/core/rpc-server/src/lib.rs b/tee-worker/core/rpc-server/src/lib.rs new file mode 100644 index 0000000000..06cab927a9 --- /dev/null +++ b/tee-worker/core/rpc-server/src/lib.rs @@ -0,0 +1,80 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +use itp_enclave_api::direct_request::DirectRequest; +use itp_rpc::RpcRequest; +use itp_utils::ToHexPrefixed; +use its_peer_fetch::block_fetch_server::BlockFetchServerModuleBuilder; +use its_primitives::types::block::SignedBlock; +use its_rpc_handler::constants::RPC_METHOD_NAME_IMPORT_BLOCKS; +use its_storage::interface::FetchBlocks; +use jsonrpsee::{ + types::error::CallError, + ws_server::{RpcModule, WsServerBuilder}, +}; +use log::debug; +use std::{net::SocketAddr, sync::Arc}; +use tokio::net::ToSocketAddrs; + +#[cfg(test)] +mod mock; +#[cfg(test)] +mod tests; + +pub async fn run_server( + addr: impl ToSocketAddrs, + enclave: Arc, + sidechain_block_fetcher: Arc, +) -> anyhow::Result +where + Enclave: DirectRequest, + FetchSidechainBlocks: FetchBlocks + Send + Sync + 'static, +{ + let mut server = WsServerBuilder::default().build(addr).await?; + + // FIXME: import block should be moved to trusted side. + let mut import_sidechain_block_module = RpcModule::new(enclave); + import_sidechain_block_module.register_method( + RPC_METHOD_NAME_IMPORT_BLOCKS, + |params, enclave| { + debug!("{} params: {:?}", RPC_METHOD_NAME_IMPORT_BLOCKS, params); + + let enclave_req = RpcRequest::compose_jsonrpc_call( + RPC_METHOD_NAME_IMPORT_BLOCKS.into(), + vec![params.one::>()?.to_hex()], + ) + .unwrap(); + + enclave + .rpc(enclave_req.as_bytes().to_vec()) + .map_err(|e| CallError::Failed(e.into())) + }, + )?; + server.register_module(import_sidechain_block_module).unwrap(); + + let fetch_sidechain_blocks_module = BlockFetchServerModuleBuilder::new(sidechain_block_fetcher) + .build() + .map_err(|e| CallError::Failed(e.to_string().into()))?; // `to_string` necessary due to no all errors implementing Send + Sync. + server.register_module(fetch_sidechain_blocks_module).unwrap(); + + let socket_addr = server.local_addr()?; + tokio::spawn(async move { server.start().await }); + + println!("[+] Untrusted RPC server is spawned on: {}", socket_addr); + + Ok(socket_addr) +} diff --git a/tee-worker/core/rpc-server/src/mock.rs b/tee-worker/core/rpc-server/src/mock.rs new file mode 100644 index 0000000000..7af48b1c63 --- /dev/null +++ b/tee-worker/core/rpc-server/src/mock.rs @@ -0,0 +1,55 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +use itp_enclave_api::{direct_request::DirectRequest, EnclaveResult}; +use itp_rpc::RpcResponse; +use itp_utils::ToHexPrefixed; +use its_primitives::{ + traits::ShardIdentifierFor, + types::{BlockHash, SignedBlock, SignedBlock as SignedSidechainBlock}, +}; +use its_storage::interface::FetchBlocks; +use parity_scale_codec::Encode; + +pub struct TestEnclave; + +impl DirectRequest for TestEnclave { + fn rpc(&self, _request: Vec) -> EnclaveResult> { + Ok(RpcResponse { jsonrpc: "mock_response".into(), result: "null".to_hex(), id: 1 }.encode()) + } +} + +pub struct MockSidechainBlockFetcher; + +impl FetchBlocks for MockSidechainBlockFetcher { + fn fetch_all_blocks_after( + &self, + _block_hash: &BlockHash, + _shard_identifier: &ShardIdentifierFor, + ) -> its_storage::Result> { + Ok(Vec::new()) + } + + fn fetch_blocks_in_range( + &self, + _block_hash_from: &BlockHash, + _block_hash_until: &BlockHash, + _shard_identifier: &ShardIdentifierFor, + ) -> its_storage::Result> { + Ok(Vec::new()) + } +} diff --git a/tee-worker/core/rpc-server/src/tests.rs b/tee-worker/core/rpc-server/src/tests.rs new file mode 100644 index 0000000000..cfb1922d0d --- /dev/null +++ b/tee-worker/core/rpc-server/src/tests.rs @@ -0,0 +1,56 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +use super::*; +use crate::mock::MockSidechainBlockFetcher; +use itp_rpc::RpcResponse; +use its_rpc_handler::constants::RPC_METHOD_NAME_IMPORT_BLOCKS; +use its_test::sidechain_block_builder::SidechainBlockBuilder; +use jsonrpsee::{ + types::{to_json_value, traits::Client}, + ws_client::WsClientBuilder, +}; +use log::info; +use mock::TestEnclave; +use parity_scale_codec::Decode; + +fn init() { + let _ = env_logger::builder().is_test(true).try_init(); +} + +#[tokio::test] +async fn test_client_calls() { + init(); + let addr = + run_server("127.0.0.1:0", Arc::new(TestEnclave), Arc::new(MockSidechainBlockFetcher)) + .await + .unwrap(); + info!("ServerAddress: {:?}", addr); + + let url = format!("ws://{}", addr); + let client = WsClientBuilder::default().build(&url).await.unwrap(); + let response: Vec = client + .request( + RPC_METHOD_NAME_IMPORT_BLOCKS, + vec![to_json_value(vec![SidechainBlockBuilder::default().build_signed()]).unwrap()] + .into(), + ) + .await + .unwrap(); + + assert!(RpcResponse::decode(&mut response.as_slice()).is_ok()); +} diff --git a/tee-worker/core/tls-websocket-server/Cargo.toml b/tee-worker/core/tls-websocket-server/Cargo.toml new file mode 100644 index 0000000000..2ab7887d4b --- /dev/null +++ b/tee-worker/core/tls-websocket-server/Cargo.toml @@ -0,0 +1,75 @@ +[package] +authors = ["Integritee AG "] +edition = "2021" +name = "itc-tls-websocket-server" +version = "0.9.0" + +[dependencies] +bit-vec = { version = "0.6", default-features = false } +chrono = { version = "0.4.19", default-features = false, features = ["alloc"] } +rcgen = { package = "rcgen", default-features = false, git = "https://github.com/integritee-network/rcgen" } + +# sgx dependencies +sgx_tstd = { optional = true, features = ["net", "thread"], git = "https://github.com/apache/teaclave-sgx-sdk.git", branch = "master" } +sgx_types = { optional = true, git = "https://github.com/apache/teaclave-sgx-sdk.git", branch = "master" } +# Todo: should not be needed here: #848 +sgx_crypto_helper = { default-features = false, optional = true, features = ["mesalock_sgx"], version = "1.1.6", git = "https://github.com/apache/incubator-teaclave-sgx-sdk", branch = "v1.1.6-testing" } + +# sgx enabled external libraries +mio-extras = { optional = true, default-features = false, git = "https://github.com/integritee-network/mio-extras-sgx", rev = "963234b" } +mio_sgx = { package = "mio", optional = true, git = "https://github.com/mesalock-linux/mio-sgx", tag = "sgx_1.1.3" } +rustls_sgx = { package = "rustls", optional = true, git = "https://github.com/mesalock-linux/rustls", branch = "mesalock_sgx" } +thiserror_sgx = { package = "thiserror", optional = true, git = "https://github.com/mesalock-linux/thiserror-sgx", tag = "sgx_1.1.3" } +tungstenite_sgx = { package = "tungstenite", optional = true, features = ["rustls-tls-webpki-roots"], git = "https://github.com/integritee-network/tungstenite-rs-sgx", branch = "sgx-experimental" } +webpki_sgx = { package = "webpki", optional = true, git = "https://github.com/mesalock-linux/webpki", branch = "mesalock_sgx" } +yasna_sgx = { package = "yasna", optional = true, default-features = false, features = ["bit-vec", "num-bigint", "chrono", "mesalock_sgx"], git = "https://github.com/mesalock-linux/yasna.rs-sgx", rev = "sgx_1.1.3" } + +# std compatible external libraries (make sure these versions match with the sgx-enabled ones above) +mio = { version = "0.6.14", optional = true } +rustls = { version = "0.19", optional = true } +thiserror = { version = "1.0", optional = true } +tungstenite = { version = "0.15.0", optional = true, features = ["rustls-tls-webpki-roots"] } +webpki = { version = "0.21", optional = true } +yasna = { version = "0.4", optional = true, features = ["bit-vec", "num-bigint", "chrono", "std"] } + +# Substrate dependencies +sp-core = { default-features = false, features = ["full_crypto"], git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.29" } + +# no-std compatible libraries +log = { version = "0.4", default-features = false } + +[dev-dependencies] +env_logger = "0.9.0" +rustls = { version = "0.19", features = ["dangerous_configuration"] } +url = { version = "2.0.0" } + + +[features] +default = ["std"] +mocks = [] +sgx = [ + "mio-extras/sgx", + "mio_sgx", + "rcgen/sgx", + "rcgen/pem_sgx", + "rustls_sgx", + "sgx_tstd", + "sgx_types", + "sgx_crypto_helper", + "thiserror_sgx", + "tungstenite_sgx", + "webpki_sgx", + "yasna_sgx", +] +std = [ + "mio", + "mio-extras/std", + "rcgen/std", + "rcgen/pem", + "rustls", + "thiserror", + "tungstenite", + "webpki", + "yasna", + "log/std", +] diff --git a/tee-worker/core/tls-websocket-server/src/certificate_generation.rs b/tee-worker/core/tls-websocket-server/src/certificate_generation.rs new file mode 100644 index 0000000000..2f7e282599 --- /dev/null +++ b/tee-worker/core/tls-websocket-server/src/certificate_generation.rs @@ -0,0 +1,172 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ +#[cfg(all(not(feature = "std"), feature = "sgx"))] +use crate::sgx_reexport_prelude::*; + +use crate::{error::WebSocketError, WebSocketResult}; +use bit_vec::BitVec; +use chrono::{prelude::*, TimeZone, Utc as TzUtc}; +use core::convert::TryFrom; +use rcgen::{date_time_ymd, Certificate, CertificateParams, DistinguishedName, DnType}; +use sp_core::{crypto::Pair, ed25519}; +use std::{ + string::ToString, + time::{SystemTime, UNIX_EPOCH}, + vec, + vec::Vec, +}; +use yasna::models::ObjectIdentifier; + +const ED25519: &[u64] = &[1, 3, 101, 112]; + +/// Create a sel-signed certificate, signed with the Ed25519 private key +/// Certificate Params are : +/// - alg: &PKCS_ED25519 -> ED25519 curve signing as per [RFC 8410](https://tools.ietf.org/html/rfc8410) +/// - common_name : the “subject”of the certificate, which is the identity of the certificate/website owner. +/// - not_before : now +/// - not_after : 4096-01-01 -> Certificate valid from initialisation time until 4096-01-01 +/// - serial_number : None, +/// - subject_alt_names : common_name. Required parameter. See below, subject +/// - DistinguishedName : +/// - issuer : Integritee, (The issuer field identifies the entity that has signed and issued the certificate. +/// The issuer field MUST contain a non-empty distinguished name (DN) ) +/// - subject: empty. (The subject field identifies the entity associated with the public key stored in the subject +/// public key field. If subject naming information is present only in the subjectAltName extension +/// (e.g., a key bound only to an email address or URI), then the subject name MUST be an empty sequence +/// and the subjectAltName extension MUST be critical. +/// - is_ca : SelfSignedOnly -> The certificate can only sign itself +/// - key_usages: empty (The key usage extension defines the purpose (e.g., encipherment, signature, certificate signing) of +/// the key contained in the certificate. The usage restriction might be employed when a key that could +/// be used for more than one operation is to be restricted.) +/// - extended_key_usages: empty ( This extension indicates one or more purposes for which the certified public key may be used, +/// in addition to or in place of the basic purposes indicated in the key usage extension.) +/// - name_constraints : None (only relevant for CA certificates) +/// - custom_extensions: None (The extensions defined for X.509 v3 certificates provide methods for associating additional +/// attributes with users or public keys and for managing relationships between CAs.) +/// - key_pair : rcgen::KeyPair from enclave private key. (A key pair used to sign certificates and CSRs) +/// - use_authority_key_identifier_extension: false (If `true` (and not self-signed), the 'Authority Key Identifier' extension will be added to the generated cert) +/// - key_identifier_method : KeyIdMethod::Sha256 (Method to generate key identifiers from public keys) + +pub fn ed25519_self_signed_certificate( + key_pair: ed25519::Pair, + common_name: &str, +) -> WebSocketResult { + let mut params = CertificateParams::new(vec![common_name.to_string()]); + let now = SystemTime::now().duration_since(UNIX_EPOCH).expect("Error: UNIX_EPOCH"); + let issue_ts = TzUtc + .timestamp_opt(now.as_secs() as i64, 0) + .single() + .expect("Error: this should not fail as long as secs fit into i64"); + let year = issue_ts.year(); + let month = issue_ts.month(); + let day = issue_ts.day(); + params.not_before = date_time_ymd(year, month, day); + params.not_after = date_time_ymd(4096, 1, 1); + let mut dn = DistinguishedName::new(); + dn.push(DnType::OrganizationName, "Integritee"); + //dn.push(DnType::CommonName, common_name); + params.distinguished_name = dn; + + params.alg = &rcgen::PKCS_ED25519; //Signature Algorithm: + + let private_key_der = ed25519_private_key_pkcs8_der(key_pair)?; + + let key_pair = rcgen::KeyPair::try_from(private_key_der.as_ref()).expect("Invalid pkcs8 der"); + params.key_pair = Some(key_pair); + + Certificate::from_params(params).map_err(|e| WebSocketError::Other(e.into())) +} + +/// Generate the private key in a PKCS#8 format. To be compatible with rcgen lib. +/// PKCS#8 is specified in [RFC 5958]. +/// +/// [RFC 5958]: https://tools.ietf.org/html/rfc5958. +fn ed25519_private_key_pkcs8_der(key_pair: ed25519::Pair) -> WebSocketResult> { + let seed = key_pair.seed(); + let private_key = seed.as_slice(); + let pk = key_pair.public().0; + let public_key = pk.as_slice(); + let key_der = yasna::construct_der(|writer| { + writer.write_sequence(|writer| { + writer.next().write_u8(1); + // write OID + writer.next().write_sequence(|writer| { + writer.next().write_oid(&ObjectIdentifier::from_slice(ED25519)); + }); + let pk = yasna::construct_der(|writer| writer.write_bytes(private_key)); + writer.next().write_bytes(&pk); + writer.next().write_tagged(yasna::Tag::context(1), |writer| { + writer.write_bitvec(&BitVec::from_bytes(public_key)) + }) + }); + }); + Ok(key_der) +} + +#[cfg(test)] +mod tests { + use crate::certificate_generation::ed25519_self_signed_certificate; + use sp_core::{crypto::Pair, ed25519}; + use std::time::SystemTime; + use webpki::TLSServerTrustAnchors; + + type Seed = [u8; 32]; + const TEST_SEED: Seed = *b"12345678901234567890123456789012"; + + #[test] + pub fn test_verify_signature_self_signed_certificate() { + let signing = signer(); + let pk = signing.public().0; + let public_key = pk.as_slice(); + let cert = ed25519_self_signed_certificate(signing, "Test").unwrap(); + let sign_pub_key = cert.get_key_pair().public_key_raw(); + assert_eq!(public_key, sign_pub_key); + } + + #[test] + pub fn test_verify_is_valid_tls_server_certificate() { + let common_name = "Test"; + let signing = signer(); + let cert = ed25519_self_signed_certificate(signing, common_name).unwrap(); + + //write certificate and private key pem file + //let cert_der = cert.serialize_der().unwrap(); + //fs::write("test_cert.der", &cert_der).unwrap(); + + let cert_der = cert.serialize_der().unwrap(); + let end_entity_cert = webpki::EndEntityCert::from(&cert_der).unwrap(); + + let time = webpki::Time::try_from(SystemTime::now()); + + let trust_anchor = webpki::trust_anchor_util::cert_der_as_trust_anchor(&cert_der).unwrap(); + let trust_anchor_list = &[trust_anchor]; + let trust_anchors = TLSServerTrustAnchors(trust_anchor_list); + + assert!(end_entity_cert + .verify_is_valid_tls_server_cert( + &[&webpki::ED25519], + &trust_anchors, + &[], + time.unwrap(), + ) + .is_ok()); + } + + fn signer() -> ed25519::Pair { + ed25519::Pair::from_seed(&TEST_SEED) + } +} diff --git a/tee-worker/core/tls-websocket-server/src/config_provider.rs b/tee-worker/core/tls-websocket-server/src/config_provider.rs new file mode 100644 index 0000000000..04d561bc20 --- /dev/null +++ b/tee-worker/core/tls-websocket-server/src/config_provider.rs @@ -0,0 +1,45 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +#[cfg(all(not(feature = "std"), feature = "sgx"))] +use crate::sgx_reexport_prelude::*; + +use crate::{error::WebSocketResult, tls_common::make_config}; +use rustls::ServerConfig; +use std::{string::String, sync::Arc}; + +/// Trait to provide a Rustls server config. +pub trait ProvideServerConfig: Send + Sync { + fn get_config(&self) -> WebSocketResult>; +} + +pub struct FromFileConfigProvider { + private_key: String, + certificate: String, +} + +impl FromFileConfigProvider { + pub fn new(private_key: String, certificate: String) -> Self { + Self { private_key, certificate } + } +} + +impl ProvideServerConfig for FromFileConfigProvider { + fn get_config(&self) -> WebSocketResult> { + make_config(&self.certificate, &self.private_key) + } +} diff --git a/tee-worker/core/tls-websocket-server/src/connection.rs b/tee-worker/core/tls-websocket-server/src/connection.rs new file mode 100644 index 0000000000..ab456236b0 --- /dev/null +++ b/tee-worker/core/tls-websocket-server/src/connection.rs @@ -0,0 +1,344 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +#[cfg(all(not(feature = "std"), feature = "sgx"))] +use crate::sgx_reexport_prelude::*; + +use crate::{ + error::WebSocketError, stream_state::StreamState, WebSocketConnection, WebSocketMessageHandler, + WebSocketResult, +}; +use log::*; +use mio::{event::Event, net::TcpStream, Poll, Ready, Token}; +use rustls::{ServerSession, Session}; +use std::{ + format, + string::{String, ToString}, + sync::Arc, + time::Instant, +}; +use tungstenite::Message; + +/// A web-socket connection object. +pub struct TungsteniteWsConnection { + stream_state: StreamState, + connection_token: Token, + connection_handler: Arc, + is_closed: bool, +} + +impl TungsteniteWsConnection +where + Handler: WebSocketMessageHandler, +{ + pub fn new( + tcp_stream: TcpStream, + server_session: ServerSession, + connection_token: Token, + handler: Arc, + ) -> WebSocketResult { + Ok(TungsteniteWsConnection { + stream_state: StreamState::from_stream(rustls::StreamOwned::new( + server_session, + tcp_stream, + )), + connection_token, + connection_handler: handler, + is_closed: false, + }) + } + + fn do_tls_read(&mut self) -> ConnectionState { + let tls_stream = match self.stream_state.internal_stream_mut() { + None => return ConnectionState::Closing, + Some(s) => s, + }; + + let tls_session = &mut tls_stream.sess; + + match tls_session.read_tls(&mut tls_stream.sock) { + Ok(r) => + if r == 0 { + return ConnectionState::Closing + }, + Err(err) => { + if let std::io::ErrorKind::WouldBlock = err.kind() { + debug!("TLS session is blocked (connection {})", self.connection_token.0); + return ConnectionState::Blocked + } + warn!( + "I/O error after reading TLS data (connection {}): {:?}", + self.connection_token.0, err + ); + return ConnectionState::Closing + }, + } + + match tls_session.process_new_packets() { + Ok(_) => { + if tls_session.is_handshaking() { + return ConnectionState::TlsHandshake + } + ConnectionState::Alive + }, + Err(e) => { + error!("cannot process TLS packet(s), closing connection: {:?}", e); + ConnectionState::Closing + }, + } + } + + fn do_tls_write(&mut self) -> ConnectionState { + let tls_stream = match self.stream_state.internal_stream_mut() { + None => return ConnectionState::Closing, + Some(s) => s, + }; + + match tls_stream.sess.write_tls(&mut tls_stream.sock) { + Ok(_) => { + trace!("TLS write successful, connection {} is alive", self.connection_token.0); + if tls_stream.sess.is_handshaking() { + return ConnectionState::TlsHandshake + } + ConnectionState::Alive + }, + Err(e) => { + error!("TLS write error (connection {}): {:?}", self.connection_token.0, e); + ConnectionState::Closing + }, + } + } + + /// Read from a web-socket, or initiate handshake if websocket is not initialized yet. + /// + /// Returns a boolean 'connection should be closed'. + fn read_or_initialize_websocket(&mut self) -> WebSocketResult { + if let StreamState::EstablishedWebsocket(web_socket) = &mut self.stream_state { + trace!( + "Read is possible for connection {}: {}", + self.connection_token.0, + web_socket.can_read() + ); + match web_socket.read_message() { + Ok(m) => + if let Err(e) = self.handle_message(m) { + error!( + "Failed to handle web-socket message (connection {}): {:?}", + self.connection_token.0, e + ); + }, + Err(e) => match e { + tungstenite::Error::ConnectionClosed => return Ok(true), + tungstenite::Error::AlreadyClosed => return Ok(true), + _ => error!( + "Failed to read message from web-socket (connection {}): {:?}", + self.connection_token.0, e + ), + }, + } + trace!("Read successful for connection {}", self.connection_token.0); + } else { + trace!("Initialize connection {}", self.connection_token.0); + self.stream_state = std::mem::take(&mut self.stream_state).attempt_handshake(); + if self.stream_state.is_invalid() { + warn!("Web-socket connection ({:?}) failed, closing", self.connection_token); + return Ok(true) + } + debug!("Initialized connection {} successfully", self.connection_token.0); + } + + Ok(false) + } + + fn handle_message(&mut self, message: Message) -> WebSocketResult<()> { + match message { + Message::Text(string_message) => { + trace!( + "Got Message::Text on web-socket (connection {}), calling handler..", + self.connection_token.0 + ); + let message_handled_timer = Instant::now(); + if let Some(reply) = self + .connection_handler + .handle_message(self.connection_token.into(), string_message)? + { + trace!( + "Handling message yielded a reply, sending it now to connection {}..", + self.connection_token.0 + ); + self.write_message(reply)?; + trace!("Reply sent successfully to connection {}", self.connection_token.0); + } + debug!( + "Handled web-socket message in {} ms", + message_handled_timer.elapsed().as_millis() + ); + }, + Message::Binary(_) => { + warn!("received binary message, don't have a handler for this format"); + }, + Message::Close(_) => { + debug!( + "Received close frame, driving web-socket connection {} to close", + self.connection_token.0 + ); + if let StreamState::EstablishedWebsocket(web_socket) = &mut self.stream_state { + // Send a close frame back and then flush the send queue. + if let Err(e) = web_socket.close(None) { + match e { + tungstenite::Error::ConnectionClosed + | tungstenite::Error::AlreadyClosed => {}, + _ => warn!( + "Failed to send close frame (connection {}): {:?}", + self.connection_token.0, e + ), + } + } + match web_socket.write_pending() { + Ok(_) => {}, + Err(e) => match e { + tungstenite::Error::ConnectionClosed + | tungstenite::Error::AlreadyClosed => {}, + _ => warn!("Failed to write pending frames after closing (connection {}): {:?}", self.connection_token.0, e), + }, + } + } + debug!("Successfully closed connection {}", self.connection_token.0); + }, + _ => {}, + } + Ok(()) + } + + pub(crate) fn write_message(&mut self, message: String) -> WebSocketResult<()> { + match &mut self.stream_state { + StreamState::EstablishedWebsocket(web_socket) => { + if !web_socket.can_write() { + return Err(WebSocketError::ConnectionClosed) + } + debug!("Write message to connection {}: {}", self.connection_token.0, message); + web_socket + .write_message(Message::Text(message)) + .map_err(|e| WebSocketError::SocketWriteError(format!("{:?}", e))) + }, + _ => + Err(WebSocketError::SocketWriteError("No active web-socket available".to_string())), + } + } +} + +impl WebSocketConnection for TungsteniteWsConnection +where + Handler: WebSocketMessageHandler, +{ + type Socket = TcpStream; + + fn socket(&self) -> Option<&Self::Socket> { + self.stream_state.internal_stream().map(|s| &s.sock) + } + + fn get_session_readiness(&self) -> Ready { + match self.stream_state.internal_stream() { + None => mio::Ready::empty(), + Some(s) => { + let wants_read = s.sess.wants_read(); + let wants_write = s.sess.wants_write(); + + if wants_read && wants_write { + mio::Ready::readable() | mio::Ready::writable() + } else if wants_write { + mio::Ready::writable() + } else { + mio::Ready::readable() + } + }, + } + } + + fn on_ready(&mut self, poll: &mut Poll, event: &Event) -> WebSocketResult<()> { + let mut is_closing = false; + + if event.readiness().is_readable() { + trace!("Connection ({:?}) is readable", self.token()); + + let connection_state = self.do_tls_read(); + + if connection_state.is_alive() { + is_closing = self.read_or_initialize_websocket()?; + } else { + is_closing = connection_state.is_closing(); + } + } + + if event.readiness().is_writable() { + trace!("Connection ({:?}) is writable", self.token()); + + let connection_state = self.do_tls_write(); + + if connection_state.is_alive() { + if let StreamState::EstablishedWebsocket(web_socket) = &mut self.stream_state { + trace!("Web-socket, write pending messages"); + if let Err(e) = web_socket.write_pending() { + match e { + tungstenite::Error::ConnectionClosed + | tungstenite::Error::AlreadyClosed => is_closing = true, + _ => error!("Failed to write pending web-socket messages: {:?}", e), + } + } + } + } else { + is_closing = connection_state.is_closing(); + } + } + + if is_closing { + debug!("Connection ({:?}) is closed", self.token()); + self.is_closed = true; + } else { + // Re-register with the poll. + self.reregister(poll)?; + } + Ok(()) + } + + fn is_closed(&self) -> bool { + self.is_closed + } + + fn token(&self) -> Token { + self.connection_token + } +} + +/// Internal connection state. +#[derive(Debug, Clone)] +enum ConnectionState { + Closing, + Blocked, + Alive, + TlsHandshake, +} + +impl ConnectionState { + pub(crate) fn is_alive(&self) -> bool { + matches!(self, ConnectionState::Alive) + } + + pub(crate) fn is_closing(&self) -> bool { + matches!(self, ConnectionState::Closing) + } +} diff --git a/tee-worker/core/tls-websocket-server/src/connection_id_generator.rs b/tee-worker/core/tls-websocket-server/src/connection_id_generator.rs new file mode 100644 index 0000000000..dac5431cb6 --- /dev/null +++ b/tee-worker/core/tls-websocket-server/src/connection_id_generator.rs @@ -0,0 +1,76 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +#[cfg(feature = "sgx")] +use std::sync::SgxRwLock as RwLock; + +#[cfg(feature = "std")] +use std::sync::RwLock; + +use crate::{error::WebSocketError, WebSocketResult}; + +pub type ConnectionId = usize; + +/// Trait to generate IDs (nonce) for websocket connections. +pub trait GenerateConnectionId { + fn next_id(&self) -> WebSocketResult; +} + +pub struct ConnectionIdGenerator { + current_id: RwLock, +} + +const MIN_ID: usize = 10; + +impl Default for ConnectionIdGenerator { + fn default() -> Self { + Self { current_id: RwLock::new(MIN_ID) } + } +} + +impl GenerateConnectionId for ConnectionIdGenerator { + fn next_id(&self) -> WebSocketResult { + let mut id_lock = self.current_id.write().map_err(|_| WebSocketError::LockPoisoning)?; + *id_lock = id_lock.checked_add(1).unwrap_or(MIN_ID); + Ok(*id_lock) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::ws_server::{NEW_CONNECTIONS_LISTENER, SERVER_SIGNAL_TOKEN}; + + #[test] + fn next_id_works() { + let id_generator = ConnectionIdGenerator::default(); + + assert_eq!(11, id_generator.next_id().unwrap()); + assert_eq!(12, id_generator.next_id().unwrap()); + assert_eq!(13, id_generator.next_id().unwrap()); + } + + #[test] + fn next_id_is_greater_than_default_tokens() { + let id_generator = ConnectionIdGenerator::default(); + + let first_id = id_generator.next_id().unwrap(); + + assert!(NEW_CONNECTIONS_LISTENER < mio::Token(first_id)); + assert!(SERVER_SIGNAL_TOKEN < mio::Token(first_id)); + } +} diff --git a/tee-worker/core/tls-websocket-server/src/error.rs b/tee-worker/core/tls-websocket-server/src/error.rs new file mode 100644 index 0000000000..3d86b509dc --- /dev/null +++ b/tee-worker/core/tls-websocket-server/src/error.rs @@ -0,0 +1,55 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +#[cfg(all(not(feature = "std"), feature = "sgx"))] +use crate::sgx_reexport_prelude::*; + +use crate::ConnectionId; +use std::{boxed::Box, io::Error as IoError, net::AddrParseError, string::String}; + +pub type WebSocketResult = Result; + +/// General web-socket error type +#[derive(Debug, thiserror::Error)] +pub enum WebSocketError { + #[error("Invalid certificate: {0}")] + InvalidCertificate(String), + #[error("Invalid private key: {0}")] + InvalidPrivateKey(String), + #[error("Invalid web-socket address: {0}")] + InvalidWsAddress(AddrParseError), + #[error("TCP bind: {0}")] + TcpBindError(IoError), + #[error("Web-socket hand shake: {0}")] + HandShakeError(String), + #[error("{0} is not a valid and active web-socket connection id")] + InvalidConnection(ConnectionId), + #[error("Web-socket connection already closed error")] + ConnectionClosed, + #[error("Web-socket connection has not yet been established")] + ConnectionNotYetEstablished, + #[error("Web-socket write: {0}")] + SocketWriteError(String), + #[error("Lock poisoning")] + LockPoisoning, + #[error("Failed to receive server signal message: {0}")] + MioReceiveError(#[from] std::sync::mpsc::TryRecvError), + #[error("{0}")] + IoError(#[from] std::io::Error), + #[error("{0}")] + Other(Box), +} diff --git a/tee-worker/core/tls-websocket-server/src/lib.rs b/tee-worker/core/tls-websocket-server/src/lib.rs new file mode 100644 index 0000000000..919e0526dc --- /dev/null +++ b/tee-worker/core/tls-websocket-server/src/lib.rs @@ -0,0 +1,177 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +#![cfg_attr(not(feature = "std"), no_std)] + +#[cfg(all(feature = "std", feature = "sgx"))] +compile_error!("feature \"std\" and feature \"sgx\" cannot be enabled at the same time"); + +#[cfg(all(not(feature = "std"), feature = "sgx"))] +extern crate sgx_tstd as std; + +// re-export module to properly feature gate sgx and regular std environment +#[cfg(all(not(feature = "std"), feature = "sgx"))] +pub mod sgx_reexport_prelude { + pub use mio_sgx as mio; + pub use rustls_sgx as rustls; + pub use thiserror_sgx as thiserror; + pub use tungstenite_sgx as tungstenite; + pub use webpki_sgx as webpki; + pub use yasna_sgx as yasna; +} + +#[cfg(all(not(feature = "std"), feature = "sgx"))] +use crate::sgx_reexport_prelude::*; + +use crate::{ + config_provider::FromFileConfigProvider, + connection_id_generator::{ConnectionId, ConnectionIdGenerator}, + error::{WebSocketError, WebSocketResult}, + ws_server::TungsteniteWsServer, +}; +use mio::{event::Evented, Token}; +use std::{ + fmt::Debug, + string::{String, ToString}, + sync::Arc, +}; + +pub mod certificate_generation; +pub mod config_provider; +mod connection; +pub mod connection_id_generator; +pub mod error; +mod stream_state; +mod tls_common; +pub mod ws_server; + +#[cfg(any(test, feature = "mocks"))] +pub mod test; + +/// Connection token alias. +#[derive(Eq, PartialEq, Clone, Copy, Debug, Hash)] +pub struct ConnectionToken(pub usize); + +impl From for Token { + fn from(c: ConnectionToken) -> Self { + Token(c.0) + } +} + +impl From for ConnectionToken { + fn from(t: Token) -> Self { + ConnectionToken(t.0) + } +} + +/// Handles a web-socket connection message. +pub trait WebSocketMessageHandler: Send + Sync { + fn handle_message( + &self, + connection_token: ConnectionToken, + message: String, + ) -> WebSocketResult>; +} + +/// Allows to send response messages to a specific connection. +pub trait WebSocketResponder: Send + Sync { + fn send_message( + &self, + connection_token: ConnectionToken, + message: String, + ) -> WebSocketResult<()>; +} + +/// Run a web-socket server with a given handler. +pub trait WebSocketServer { + type Connection; + + fn run(&self) -> WebSocketResult<()>; + + fn is_running(&self) -> WebSocketResult; + + fn shut_down(&self) -> WebSocketResult<()>; +} + +/// Abstraction of a web socket connection using mio. +pub(crate) trait WebSocketConnection: Send + Sync { + /// Socket type, typically a TCP stream. + type Socket: Evented; + + /// Get the underlying socket (TCP stream) + fn socket(&self) -> Option<&Self::Socket>; + + /// Query the underlying session for readiness (read/write). + fn get_session_readiness(&self) -> mio::Ready; + + /// Handles the ready event, the connection has work to do. + fn on_ready(&mut self, poll: &mut mio::Poll, ev: &mio::event::Event) -> WebSocketResult<()>; + + /// True if connection was closed. + fn is_closed(&self) -> bool; + + /// Return the connection token (= ID) + fn token(&self) -> mio::Token; + + /// Register the connection with the mio poll. + fn register(&mut self, poll: &mio::Poll) -> WebSocketResult<()> { + match self.socket() { + Some(s) => { + poll.register( + s, + self.token(), + self.get_session_readiness(), + mio::PollOpt::level() | mio::PollOpt::oneshot(), + )?; + Ok(()) + }, + None => Err(WebSocketError::ConnectionClosed), + } + } + + /// Re-register the connection with the mio poll, after handling an event. + fn reregister(&mut self, poll: &mio::Poll) -> WebSocketResult<()> { + match self.socket() { + Some(s) => { + poll.reregister( + s, + self.token(), + self.get_session_readiness(), + mio::PollOpt::level() | mio::PollOpt::oneshot(), + )?; + + Ok(()) + }, + None => Err(WebSocketError::ConnectionClosed), + } + } +} + +pub fn create_ws_server( + addr_plain: &str, + private_key: &str, + certificate: &str, + handler: Arc, +) -> Arc> +where + Handler: WebSocketMessageHandler, +{ + let config_provider = + Arc::new(FromFileConfigProvider::new(private_key.to_string(), certificate.to_string())); + + Arc::new(TungsteniteWsServer::new(addr_plain.to_string(), config_provider, handler)) +} diff --git a/tee-worker/core/tls-websocket-server/src/stream_state.rs b/tee-worker/core/tls-websocket-server/src/stream_state.rs new file mode 100644 index 0000000000..ef53a14b61 --- /dev/null +++ b/tee-worker/core/tls-websocket-server/src/stream_state.rs @@ -0,0 +1,105 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +#[cfg(all(not(feature = "std"), feature = "sgx"))] +use crate::sgx_reexport_prelude::*; + +use log::*; +use mio::net::TcpStream; +use rustls::ServerSession; +use std::boxed::Box; +use tungstenite::{ + accept, + handshake::{server::NoCallback, MidHandshake}, + HandshakeError, ServerHandshake, WebSocket, +}; + +pub(crate) type RustlsStream = rustls::StreamOwned; +pub(crate) type RustlsServerHandshake = ServerHandshake; +pub(crate) type RustlsMidHandshake = MidHandshake; +pub(crate) type RustlsWebSocket = WebSocket; + +/// Internal TLS stream state. From pure TLS stream, to web-socket handshake and established WS. +pub(crate) enum StreamState { + Invalid, + TlsStream(Box), + WebSocketHandshake(RustlsMidHandshake), + EstablishedWebsocket(Box), +} + +impl Default for StreamState { + fn default() -> Self { + Self::Invalid + } +} + +impl StreamState { + pub(crate) fn from_stream(stream: RustlsStream) -> Self { + StreamState::TlsStream(Box::new(stream)) + } + + pub(crate) fn is_invalid(&self) -> bool { + matches!(self, StreamState::Invalid) + } + + pub(crate) fn internal_stream(&self) -> Option<&RustlsStream> { + match self { + StreamState::TlsStream(s) => Some(s), + StreamState::WebSocketHandshake(h) => Some(h.get_ref().get_ref()), + StreamState::EstablishedWebsocket(ws) => Some(ws.get_ref()), + StreamState::Invalid => None, + } + } + + pub(crate) fn internal_stream_mut(&mut self) -> Option<&mut RustlsStream> { + match self { + StreamState::TlsStream(s) => Some(s), + StreamState::WebSocketHandshake(h) => Some(h.get_mut().get_mut()), + StreamState::EstablishedWebsocket(ws) => Some(ws.get_mut()), + StreamState::Invalid => None, + } + } + + pub(crate) fn attempt_handshake(self) -> Self { + match self { + // We have the bare TLS stream only, attempt to do a web-socket handshake. + StreamState::TlsStream(tls_stream) => Self::from_handshake_result(accept(*tls_stream)), + // We already have an on-going handshake, attempt another try. + StreamState::WebSocketHandshake(hs) => Self::from_handshake_result(hs.handshake()), + _ => self, + } + } + + fn from_handshake_result( + handshake_result: Result>, + ) -> Self { + match handshake_result { + Ok(ws) => Self::EstablishedWebsocket(Box::new(ws)), + Err(e) => match e { + // I/O would block our handshake attempt. Need to re-try. + HandshakeError::Interrupted(mhs) => { + info!("Web-socket handshake interrupted"); + Self::WebSocketHandshake(mhs) + }, + HandshakeError::Failure(e) => { + error!("Web-socket handshake failed: {:?}", e); + Self::Invalid + }, + }, + } + } +} diff --git a/tee-worker/core/tls-websocket-server/src/test/fixtures/mod.rs b/tee-worker/core/tls-websocket-server/src/test/fixtures/mod.rs new file mode 100644 index 0000000000..6790e464c8 --- /dev/null +++ b/tee-worker/core/tls-websocket-server/src/test/fixtures/mod.rs @@ -0,0 +1,22 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +pub mod no_cert_verifier; +pub mod test_cert; +pub mod test_private_key; +pub mod test_server; +pub mod test_server_config_provider; diff --git a/tee-worker/core/tls-websocket-server/src/test/fixtures/no_cert_verifier.rs b/tee-worker/core/tls-websocket-server/src/test/fixtures/no_cert_verifier.rs new file mode 100644 index 0000000000..50e05527ab --- /dev/null +++ b/tee-worker/core/tls-websocket-server/src/test/fixtures/no_cert_verifier.rs @@ -0,0 +1,51 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +use log::debug; +use rustls::{Certificate, ClientCertVerified, DistinguishedNames, TLSError}; +use webpki::DNSName; + +/// Test Rustls verifier, disables ALL verification (do NOT use in production!) +pub struct NoCertVerifier {} + +impl rustls::ServerCertVerifier for NoCertVerifier { + fn verify_server_cert( + &self, + _: &rustls::RootCertStore, + _: &[rustls::Certificate], + _: webpki::DNSNameRef<'_>, + _: &[u8], + ) -> Result { + debug!("Certificate verification bypassed"); + Ok(rustls::ServerCertVerified::assertion()) + } +} + +impl rustls::ClientCertVerifier for NoCertVerifier { + fn client_auth_root_subjects(&self, _sni: Option<&DNSName>) -> Option { + None + } + + fn verify_client_cert( + &self, + _presented_certs: &[Certificate], + _sni: Option<&DNSName>, + ) -> Result { + debug!("Certificate verification bypassed"); + Ok(rustls::ClientCertVerified::assertion()) + } +} diff --git a/tee-worker/core/tls-websocket-server/src/test/fixtures/test_cert.rs b/tee-worker/core/tls-websocket-server/src/test/fixtures/test_cert.rs new file mode 100644 index 0000000000..1b94e7a24a --- /dev/null +++ b/tee-worker/core/tls-websocket-server/src/test/fixtures/test_cert.rs @@ -0,0 +1,139 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +use rustls::{internal::pemfile::certs, Certificate}; +use std::{io::BufReader, vec::Vec}; + +pub fn get_test_certificate_chain() -> Vec { + let mut buf_reader = BufReader::new(CERT_STR.as_bytes()); + certs(&mut buf_reader).unwrap() +} + +const CERT_STR: &str = "\ +-----BEGIN CERTIFICATE----- +MIIEADCCAmigAwIBAgICAcgwDQYJKoZIhvcNAQELBQAwLDEqMCgGA1UEAwwhcG9u +eXRvd24gUlNBIGxldmVsIDIgaW50ZXJtZWRpYXRlMB4XDTE3MDQxMDIwNTYyN1oX +DTIyMTAwMTIwNTYyN1owGTEXMBUGA1UEAwwOdGVzdHNlcnZlci5jb20wggEiMA0G +CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCa4nonCxArES+kBBf9mZoaQ2GBMg74 +Pj2ve4RKJSIBt9A7EgJ4hFznFQ11O11Xvb3dVQGOK+pFRxh2xg0DJvV3lJytpvKe +mviyT5KSGvp6Hybqmx66B2V3iDfrXhhySqG5tKEeczFBIq+62dAp0+r0oSdpZKGT +1YDtXonjcbnDb93K7g8arEadFKYN3MAjBGQ3m5fsWJJuq4hLU1+dpmAfxmYH1dlc +n89LyPhYh0I7R5v17VrGlNCWIWD1emLtM8vTS94eMtp8R6MuMIZTOKgBTrIpU4G5 +GPcR3flDzzLsCxEttjjMa41zStKXzieUIwirRAzPv48V4JlkCCUPv97pAgMBAAGj +gb4wgbswDAYDVR0TAQH/BAIwADALBgNVHQ8EBAMCBsAwHQYDVR0OBBYEFNn77YZg +4AGguHBKVggK00dtRvhCMEIGA1UdIwQ7MDmAFGuwcG2Zfyr92yAiXU9HP9rBYC6/ +oR6kHDAaMRgwFgYDVQQDDA9wb255dG93biBSU0EgQ0GCAXswOwYDVR0RBDQwMoIO +dGVzdHNlcnZlci5jb22CFXNlY29uZC50ZXN0c2VydmVyLmNvbYIJbG9jYWxob3N0 +MA0GCSqGSIb3DQEBCwUAA4IBgQB4xB9IPNxkJIA8QtngQZCCSPH5SjfAibcLfwi2 +NLHe4hO4HvoIVv0ru7CODfq45qNfH7sUj8a/JBU8BwcJ3xPewWFdavtCP8+dapmd +pr831+Xx6p9tNIdW16WrCXEV8i9bHy43Y4pWbNdXQy5meI0qvSM/ExedZqqVeJJT +oXL/aCtMsBixlwlKvrsG9ZvIAl1ics0wA5kqQWVufe95loI+HUcPc9s9689H+/ON +lH8rTLPwyufk9h2dTb9Wzw3qewlDIqgoyX7k9cOwrJqA4D6typCvb5dWfQlK9c72 +4rGbqHSx7mrlaZ4typfAMdEbynRlDSgIIZGXb7RaoV3NT2XuVFd8+lcXgBiJMvPk +STejz77EPR2+uKvQ1gMJXpEHCBUvMMyDqhpcNzb0DaXgf4eYI9RqfxU1pkgYnfxe +DGDGI2SdmO43NwSDyEQVSlRpCIBj4ZDay3IP7mbdi8MLxR9H1BCHnN7D04UrTnuA +c/cl0RMWL+iHtKU2cCxltEQQ9qQ= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIGnzCCAoegAwIBAgIBezANBgkqhkiG9w0BAQsFADAaMRgwFgYDVQQDDA9wb255 +dG93biBSU0EgQ0EwHhcNMTcwNDEwMjA1NjI3WhcNMjcwNDA4MjA1NjI3WjAsMSow +KAYDVQQDDCFwb255dG93biBSU0EgbGV2ZWwgMiBpbnRlcm1lZGlhdGUwggGiMA0G +CSqGSIb3DQEBAQUAA4IBjwAwggGKAoIBgQDCX7V0gKGQBS64QKntjDlBslbQJaxq +EL8Yyq+qjF6nkOoqENKWSzeNyQ76kPVlzeV03UCaIgTF4+FeQrUr7wauEz0FGmDh +yx/B4xy9ZXdBIftPB8iz8Q/KrKO6YM6tkj7ijvL8hP3MfssBkA+VoAxamPSIikfM +9kyttemjYizgM0ywebzKmQGJbEINZ80Kp63ayR/Uo/cORjlH3xbmtTsL3pd+k6Ro +xOMZKm1RIwOwGgxDW4ea294A4lXHwfwHGMsP0/xmqTZ0R/EpxLKeqJAQffTiVsBK +YEFzANn3nol1IYrdcZcgcs16KTnc5+XyL87KSdIgDgG3wmQvRCdLX5G6GChyP03Z +qQSYMkwGSNgCD1v4m14Z5XT2su7iilHfjsucvT4OukCe63nqeXIZ+w63YqbjTp/a +HMgrXVg1wMlSncl0OIKcjLOgJ5vbPOGk9DvF93JbRFp/9sAZmK89Ur4gBmgpq2Zn +bknK0LVt+aerP7rf8CPYE89olPVUW0owwrkCAwEAAaNeMFwwHQYDVR0OBBYEFGuw +cG2Zfyr92yAiXU9HP9rBYC6/MCAGA1UdJQEB/wQWMBQGCCsGAQUFBwMBBggrBgEF +BQcDAjAMBgNVHRMEBTADAQH/MAsGA1UdDwQEAwIB/jANBgkqhkiG9w0BAQsFAAOC +BAEARD9wwIHsAFWlzrRLw3JcAUDB906Ks7KZzdqe3n6FkbyHHP1N5JG25wXwgADS +qux6sZU7h6+q209IEqXWbw+nbxJs/3D1hLN6X2tVgsfkSflGsd3DfNPZI8qKUyOD +VYlql/EPEMBixXOeVpwxXc48rX/yVjxqCvhY/A7eIiAc+bzQtwozLppChyVitQGI +MViXRdGdFiybwTKoJMYXl6ztamk9TWhdvJ9znirol12b06Z3J0Kz0c/kqY7VVZqL +ba76+IAJjvWQE7PYEOqpFHOLpilv9j5d/0kBR4AgJaooFwcYnr6aJKfNUgGWEmdn +ELYmfa0qORllAM/yGoewRfWGLZBNgT0QFYg2IFjnp0W0wIXFRd7xVqldN+cTmMqk +szpVV7bqGvuk6SQNFjIZ8VIVc/mXua4WlwBODDRzKqU3bIgBTODgVq1edwqp6UjN +ECLAOe1p03GGMr4WSPDoFjlQlHy+NLUwZg3RI+HsAkow9WfP7KqGN4vFDC4ru9Pg +2uD28oTrOgYQpzKjQJSH3kC5feOUdrsET7zic75XO1J33CAlgbIZ2TSQDqnH2cY5 +bQsWSNA2Lle3wBbeHlCy7ACiaoeJS23TJV9n8PcsRwSmHA9NgT4WSavXwtZ0lBhI +60GY80VXo9ziQjvVTMZNymZ4FEqCvULHGhFI08Jqd1jOXjnPLY4WEARqkicBJvI1 +3t4sBLDU+PEqH7m8k3lCZd6D7XVDcc8bJock+DjXZIMbZY79UMuzyHocXNJpRfRT +cqS0qneltFe6Pea7y0PN2IDttGBLb1CVQpXhRkpFU8jtyXh3ulSZSJEeqLVRFgdv +PVwHWAhLPewVGDkgTrlWVNfiXxp1LWVTFzQFas9xWiY4byQk/DNQaaFwHpGoZgVc +qAzUVk20Msm2u9xvSbPcBGk0dL4fdlnOkyeq/k/fnNrGdRHJWuJe7QR73/N0u6fy +7H76xUXvcwwrxL8ma8nV9K+A7oM7YUiR1wagD9cnoDDBgQmH9Izvfw0PxJgqnLOe +lQGPVGRhmXNtLLG57dqgjrvERGy9u5NMxBlkH0giZTFyQXPQ+N75ouM4S3RL75PM +UaTOBtnyCj++5ysnDFlGqEXgy08rrtkCbbNfd9dnO568juXS6ExC6TEL/pUMhy+Z +ooIJ69Tt7R5dOLaKRrkX/nKHfCfLfXXnjyDmdRHRYrXvTWusF038OsqY89tb0F0u +S4Szv4/Bl1bhzx/XYMZv/y7XL0va8FQLiRTuvqJ9hTsE/Xkd4ZFrP1LaP6HzVR1g +tsFs2Gc8j7H299U3WLjNon0TL2uPXa77Vu+9h7QCi1W9Uzsv0xMvZ/KMEnXyaEBd +W1lqo85ih1nnfxcW+lmAz8QNGQ== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIJCjCCBPKgAwIBAgIJAI+QZnVEkxq/MA0GCSqGSIb3DQEBCwUAMBoxGDAWBgNV +BAMMD3Bvbnl0b3duIFJTQSBDQTAeFw0xNzA0MTAyMDU2MjdaFw0yNzA0MDgyMDU2 +MjdaMBoxGDAWBgNVBAMMD3Bvbnl0b3duIFJTQSBDQTCCBCIwDQYJKoZIhvcNAQEB +BQADggQPADCCBAoCggQBAMD0iyFqRCNhvD5T9WXO8caNGb5ecrlnqSUvfcc+6Xh9 +sShtK6DX2DZ+6YT2WWOZTk0I9T+XG2kujjyfdCbEDMWcT9so7/gPeIG/qFlhONCu +HC+zntuZrGgMEYpF3Xc41CyF3saredTJEo1J64TPEke8mohezIGZYM1vTtRnqW+1 +RstSNTu8a/B0VaG0iA5P5RuSGVmxczi4EWJtuXFhcbgqICaUt0vJdrU0Fmrmq0Iq +ZEIpgZKYirx5QW8b6Q5tv0YsnXNasXvHZQve4GgF449ewk9wWfYevD8UttHUEe2a +QeEKb2l7NxqyY6trGyVtTRlm4SnoOH/9VodTKUEmS6pds6XFtjRflxgom0TL7CXb +uJ9b6fkXQlnf01FqAbv5HC1sjgGlSZc7Yk8k09nWOR8mZMoHC+U4KEq+oM+m87q4 +U/GsEk8UsPslGIIHHK6W/sdU6zA9bR3QYmkD40Z7FbVfKVvDmKPlwI7NONqysD8V +UTPoB8aE7FeulZhlTxdK2EcW14AsjbFiPQ4zAVxj4bRj39RLgJYL+BvAF6PfRHb1 +Xb7ykbuTvT7VhNYXLlQagR9EyixT3Wu9WCWUc0xJKSATn1s2YBLNM7LO4MkYO9WG +YrejhNHG+54a7rtnnlG04Gs7OhM32baMH/DxT+EEAX4j0Dfww4RaCZcfq1gDPsVe ++RzqsjjqF8+IzE25SK38xgwT/o3n9r5Ele3/zadwy695KCfbkhVFSDAPvhiv8um5 +6NNP+dDymFRXGzV85xSK75ue3Dpj+MoSScmIdGLEcU5EqYcBFLCXGLYPDIW8Lb89 +mG1z7TkZOLIs+6v7kp4rrvyijsyLFZ+EKUmabAK42qdzASZ1o6ETDDfFBETMxjWA +oMmGmRkhsyfBTuCr1ESlTBQHj4vvxBrgXgHtHwUinBw/sofLbkFRZ4wz/cBOtwqW +HIu88/o33l6ywMowcjaoToIbK2a4rD/KFJiwLliGKZG2veiESRhnNUQyjxT/PIef +0gqx3i1eBGWvfQs/wUC8qI5UadTRhjMFCwMCDVycevZE8lcQ+7zi9tVu6mXife5J +yP/jxRNDLzpdM6C6puqk0XieZey782XZ7sPpDpS2tphwakINF/5X3t1qZsssZPqq +F1S2VIsL8qm6Z7HDHXex3o2tDUhc226YSp/T7D+IWP3UCs0NjJrldakhnAd7ykxT +b2cDh09GDYSbji4Y6WmgIbSAurqk6kt4MWrfx4yfEAlp8ujH6788lRDAiXN1RgzC +k8r21IOJONDG7Qk1rS0YUV4XyGz4SEpBdPTI7RM1fl5bDn4e+OslBcfWh5XplZrz +4D4Z9YWVZ8X6d7CiPYZIg35oo/45KGel3Z8algziVkMCAwEAAaNTMFEwHQYDVR0O +BBYEFOWXlO0crUtBejJo87v9lwg8PlE6MB8GA1UdIwQYMBaAFOWXlO0crUtBejJo +87v9lwg8PlE6MA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggQBADUQ +YqVmS9o3/5Qp7Tr+JL5ZNRP1fRmV5kAqaKDC5I9ONKRYahHvoI0ojE/j+pmwI4gf +mp5mW8EgsNfooovrzVSHxJgBO9r1ogMlL9AvwlvVxLbexvLXpM/1QjD/7FID/TaK +1q5mhSBKaEYXqQ+8DN25aVsI/bwHx4eP11Ft6YjhPqaX/WutE/IIAMbgASRFtBlO +foTm++fpdn8rCg5LiLLpWrRLC3zUSUtFd7if3wQ4vcDdck09v9PjD5Lk34aYkowJ +oARbVmBMpAxwkMXaThP1fT7xlYPDhAA26UXksT5xUNzFPbmOVReuFT0drhJlF6e6 +SLTjy2BcrYuz5ieBmmY6QboBYH3SzUFKuamvnHLSic3i3u9Ly68XUjOtDKgYB7Y5 +oZtfZT+YFmz/R6eLUcGRRfcmLJ+i/OXjgyKVkYBMDafW3RI9fRp46Yr/lvOv5gFW +Vrn3Tfc9cSbYQgE4vuKXcs4aVVeX8uAyjcucMV3eLdxaBLUAezTpJseRfqtH2kCk +3JIV6m2y6Tm5EhhaSiHKbe6FtPFKhpu7m9AlquUzhBU9Aq59mbKp6jtV0mWhYwKB +K6REmWQqqAOtHIs7UIXDeN1ZByJ7q+et57RvMgMHc5My0d6a+gQAUssH4i73sVTz +Uej57DW9L7hK0GQpzGzGIO/9lYTzWMVa8EZG1Fa5nUgMh3N3Oy6qUQIqr8E8xT2O +IbKKV6Acx6lBiwii4JkruEMgVVEdsDWDVdP8Ov5lJvvIPLWLqnXsZ2sKCyZrVkgc +PTXVtYBLmn7Tuwody2MSaBONSqleJ1oPQJ9lsAKyqX4xpX05ZJu2kNhST2oq2127 +378GS85DqKDM3P187mjU2G8moqWaGKr6byiIr7ea5TkqIzpC3tKW5QRHvX9aanz0 +akQx6F+l3l4L8J0cXaKasUJTaCk3cWPbbVzo8tQwwdxd0/MdJWrmitK85o+4gLqG +Cvn9VA4mnhjRR0XccxEtzmhSxBRWXoCF1+FnfDmXhPji+AmAhVqRwPkqX9T9H+54 +YG2ZA9Trxssme+QFSFCPZrHuw66ZI6GmKo6h+Hr2qew7LytASN+x2QyvRf7tSNmf +oUgmiD+CFpaH6exjrCC0/hcJ53Kv3E5GBvQskvOqgsUkW+nmsrm95YOosn+9MoQc +PIM6zQCmZ0N/6jHrEHnOnSnz03tGHsvPs6tMB6DKhQz9FNqlrLG7UHhlqhFWj9nv +H+Zh0oOwbcgcoxkk+W6LHLDpA3UpC1tlOzTlD2ektACvQQr/2A/fecpJN/7iWlX9 +BimWwRTS24bO5dX92Kb8V1TNO6ARd9TqOkPXRatysyh7it/MXpc5I2+t49hqlXoV +9Xpi4ds6s2cT8zZGDKI= +-----END CERTIFICATE-----"; diff --git a/tee-worker/core/tls-websocket-server/src/test/fixtures/test_private_key.rs b/tee-worker/core/tls-websocket-server/src/test/fixtures/test_private_key.rs new file mode 100644 index 0000000000..0e3ad60d01 --- /dev/null +++ b/tee-worker/core/tls-websocket-server/src/test/fixtures/test_private_key.rs @@ -0,0 +1,53 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +use rustls::{internal::pemfile::rsa_private_keys, PrivateKey}; +use std::io::BufReader; + +pub fn get_test_private_key() -> PrivateKey { + let mut buf_reader = BufReader::new(PRIVATE_KEY_STR.as_bytes()); + rsa_private_keys(&mut buf_reader).unwrap().first().unwrap().clone() +} + +const PRIVATE_KEY_STR: &str = "\ +-----BEGIN RSA PRIVATE KEY----- +MIIEpAIBAAKCAQEAmuJ6JwsQKxEvpAQX/ZmaGkNhgTIO+D49r3uESiUiAbfQOxIC +eIRc5xUNdTtdV7293VUBjivqRUcYdsYNAyb1d5Scrabynpr4sk+Skhr6eh8m6pse +ugdld4g3614YckqhubShHnMxQSKvutnQKdPq9KEnaWShk9WA7V6J43G5w2/dyu4P +GqxGnRSmDdzAIwRkN5uX7FiSbquIS1NfnaZgH8ZmB9XZXJ/PS8j4WIdCO0eb9e1a +xpTQliFg9Xpi7TPL00veHjLafEejLjCGUzioAU6yKVOBuRj3Ed35Q88y7AsRLbY4 +zGuNc0rSl84nlCMIq0QMz7+PFeCZZAglD7/e6QIDAQABAoIBAQCEe5i08Nehnw+7 +Ie1LdSnFsUEj+6emW8bz5ZlguqZ+BbbN8DfA0qeM2gsq7d6IALr5KY8tBw9atteM +MRhMS/THloz2VMlPNYvpKftbkkwSTbdCEfGUemMmfZQnddM/X+s6J/FxVGMbLgpW +r51JSgW9vmMx2WwEQioH4EfeDxcwvZi3LF7SAo89eMSiSDqHZaIfMRmS0cSpoXav +u7gKDt7H+zSeYdLC4FhD4f8zRUpZEa4x5GIIm2JHsvIWuy9XKyepakaObJkWWqR1 +ATO94LtM2+RRVUev+yOVDDOfJtDzEqZrbokCHaVBYXgliAV/XkvFox1ZINyeGFq4 +kAvqfiQJAoGBAMhO/tAz2TpWeETMcujBekx1JmtDEUITJroDT0DvFDV5QRKVopxY +ZY5pPbwtk60KknBbsXrswR3Vh1q3xfKLT3Ln4x121ufltIwN7eopY9dXVqh830CU +QymtUz5VcvG3foWCeABcyklpZIdhHyDDDDP46URfFr3NnQiRnx7qb6yPAoGBAMXy +bSGgnBPUOWHtNW4hI5vxiOiCGWvCq7jERixybGMU8+kP6eRWUEAnOdCibq84A6gv +GLO5EW+bmL8l7L797w6ZN9DhbuR7W7hQVwdkyQS8PUgmTfsaba7+9hTC0chl+L38 +A7NlYRju+JS99SqarGA6WMvo30ykiMGwxw8tHOkHAoGAPT6Z/oK72nBx2WdBgxUV +FaeEFaut7Sv53UoBw3LWFPt7//isfW0xr/dRnuW4j2H6IEyI2XLmIP8WoZAq/9vE +cPeho3KghsrfByuDIOOC2Wak4mM7x30NhAKwvxBVUr6t+phHpKS6XPPSfuodIGFC +q+lhOTxxsZradrI/mq5HctUCgYEAqo4bYeIVGTC+0JWmd+Gt4OvYXx3Z8XOmqmjT +XfCpWyXuk13W1ZtZQi2KLy4F2IuW+w65ZgGL+HJExk5TEq2RkS6LXTsgZVW0zbbL +hd9dJOtckhIPFtDKuQGN3o2OW/EgxfGi7qvnYahmHyMdXzwuUitz3x4jaNJL0zgS +DA1+33kCgYA1iAZ58XXJPh6YObvw+kg21dCLLelxp+mCoRBSbY6wq+R6PmKg4a1N +oOc6Rh/1teyBVWJ/KnkXBeh9//XLfhg0r6zHDSCsDKabeM0eoB1AKWlc5f6bWYHV +60JHDgby+V1AElKT2yQT8KVv1hWJH4XQ1/fTQpQDDoo6O+nj1r4q6w== +-----END RSA PRIVATE KEY-----"; diff --git a/tee-worker/core/tls-websocket-server/src/test/fixtures/test_server.rs b/tee-worker/core/tls-websocket-server/src/test/fixtures/test_server.rs new file mode 100644 index 0000000000..6992b27e71 --- /dev/null +++ b/tee-worker/core/tls-websocket-server/src/test/fixtures/test_server.rs @@ -0,0 +1,41 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +use crate::{ + test::{ + fixtures::test_server_config_provider::TestServerConfigProvider, + mocks::web_socket_handler_mock::WebSocketHandlerMock, + }, + TungsteniteWsServer, +}; +use std::{string::String, sync::Arc}; + +pub type TestServer = TungsteniteWsServer; + +pub fn create_server( + handler_responses: Vec, + port: u16, +) -> (Arc, Arc) { + let config_provider = Arc::new(TestServerConfigProvider {}); + let handler = Arc::new(WebSocketHandlerMock::from_response_sequence(handler_responses)); + + let server_addr_string = format!("127.0.0.1:{}", port); + + let server = + Arc::new(TungsteniteWsServer::new(server_addr_string, config_provider, handler.clone())); + (server, handler) +} diff --git a/tee-worker/core/tls-websocket-server/src/test/fixtures/test_server_config_provider.rs b/tee-worker/core/tls-websocket-server/src/test/fixtures/test_server_config_provider.rs new file mode 100644 index 0000000000..7f267aadf5 --- /dev/null +++ b/tee-worker/core/tls-websocket-server/src/test/fixtures/test_server_config_provider.rs @@ -0,0 +1,43 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +use crate::{ + config_provider::ProvideServerConfig, + test::fixtures::{ + test_cert::get_test_certificate_chain, test_private_key::get_test_private_key, + }, + WebSocketResult, +}; +use rustls::{NoClientAuth, ServerConfig}; +use std::sync::Arc; + +pub struct TestServerConfigProvider; + +impl ProvideServerConfig for TestServerConfigProvider { + fn get_config(&self) -> WebSocketResult> { + let mut config = rustls::ServerConfig::new(NoClientAuth::new()); + + let certs = get_test_certificate_chain(); + let privkey = get_test_private_key(); + + config + .set_single_cert_with_ocsp_and_sct(certs, privkey, vec![], vec![]) + .unwrap(); + + Ok(Arc::new(config)) + } +} diff --git a/tee-worker/core/tls-websocket-server/src/test/mocks/mod.rs b/tee-worker/core/tls-websocket-server/src/test/mocks/mod.rs new file mode 100644 index 0000000000..fd5dff2b6c --- /dev/null +++ b/tee-worker/core/tls-websocket-server/src/test/mocks/mod.rs @@ -0,0 +1,19 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +pub mod web_socket_connection_mock; +pub mod web_socket_handler_mock; diff --git a/tee-worker/core/tls-websocket-server/src/test/mocks/web_socket_connection_mock.rs b/tee-worker/core/tls-websocket-server/src/test/mocks/web_socket_connection_mock.rs new file mode 100644 index 0000000000..24620c9af2 --- /dev/null +++ b/tee-worker/core/tls-websocket-server/src/test/mocks/web_socket_connection_mock.rs @@ -0,0 +1,103 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +use crate::{error::WebSocketResult, WebSocketConnection}; +use mio::{Event, Evented, Poll, PollOpt, Ready, Token}; +use std::vec::Vec; +use tungstenite::Message; + +/// Mock implementation of a web socket connection. +#[derive(PartialEq, Eq, Clone)] +pub(crate) struct WebSocketConnectionMock { + pub id: Token, + pub messages_to_read: Vec, + pub messages_written: Vec, + pub is_closed: bool, + socket: SocketMock, +} + +impl WebSocketConnectionMock { + #[allow(unused)] + pub fn new(id: Token) -> Self { + WebSocketConnectionMock { + id, + messages_to_read: Default::default(), + messages_written: Default::default(), + is_closed: false, + socket: SocketMock {}, + } + } + + #[allow(unused)] + pub fn with_messages_to_read(mut self, messages: Vec) -> Self { + self.messages_to_read = messages; + self + } +} + +impl WebSocketConnection for WebSocketConnectionMock { + type Socket = SocketMock; + + fn socket(&self) -> Option<&Self::Socket> { + Some(&self.socket) + } + + fn get_session_readiness(&self) -> Ready { + Ready::readable() + } + + fn on_ready(&mut self, _poll: &mut Poll, _ev: &Event) -> WebSocketResult<()> { + Ok(()) + } + + fn is_closed(&self) -> bool { + self.is_closed + } + + fn token(&self) -> Token { + self.id + } +} + +#[derive(PartialEq, Eq, Clone)] +pub(crate) struct SocketMock; + +impl Evented for SocketMock { + fn register( + &self, + _poll: &Poll, + _token: Token, + _interest: Ready, + _opts: PollOpt, + ) -> std::io::Result<()> { + Ok(()) + } + + fn reregister( + &self, + _poll: &Poll, + _token: Token, + _interest: Ready, + _opts: PollOpt, + ) -> std::io::Result<()> { + Ok(()) + } + + fn deregister(&self, _poll: &Poll) -> std::io::Result<()> { + Ok(()) + } +} diff --git a/tee-worker/core/tls-websocket-server/src/test/mocks/web_socket_handler_mock.rs b/tee-worker/core/tls-websocket-server/src/test/mocks/web_socket_handler_mock.rs new file mode 100644 index 0000000000..26d9b3d61c --- /dev/null +++ b/tee-worker/core/tls-websocket-server/src/test/mocks/web_socket_handler_mock.rs @@ -0,0 +1,68 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +#[cfg(feature = "sgx")] +use std::sync::SgxRwLock as RwLock; + +#[cfg(feature = "std")] +use std::sync::RwLock; + +use crate::{ConnectionToken, WebSocketMessageHandler, WebSocketResult}; +use log::debug; +use std::{collections::HashMap, string::String, vec::Vec}; + +pub struct WebSocketHandlerMock { + pub responses: Vec, + pub connection_message_indices: RwLock>, + pub messages_handled: RwLock>, +} + +impl WebSocketHandlerMock { + pub fn from_response_sequence(responses: Vec) -> Self { + WebSocketHandlerMock { + responses, + connection_message_indices: RwLock::default(), + messages_handled: Default::default(), + } + } + + pub fn get_handled_messages(&self) -> Vec<(ConnectionToken, String)> { + self.messages_handled.read().unwrap().clone() + } +} + +impl WebSocketMessageHandler for WebSocketHandlerMock { + fn handle_message( + &self, + connection_token: ConnectionToken, + message: String, + ) -> WebSocketResult> { + let mut handled_messages_lock = self.messages_handled.write().unwrap(); + + debug!("Handling message: {}", message); + handled_messages_lock.push((connection_token, message)); + + let mut connection_indices_lock = self.connection_message_indices.write().unwrap(); + + let message_index = connection_indices_lock.entry(connection_token).or_insert(0usize); + + let response = self.responses.get(*message_index).cloned(); + + *message_index += 1; + Ok(response) + } +} diff --git a/tee-worker/core/tls-websocket-server/src/test/mod.rs b/tee-worker/core/tls-websocket-server/src/test/mod.rs new file mode 100644 index 0000000000..0d2c1da1d4 --- /dev/null +++ b/tee-worker/core/tls-websocket-server/src/test/mod.rs @@ -0,0 +1,19 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +pub mod fixtures; +pub mod mocks; diff --git a/tee-worker/core/tls-websocket-server/src/tls_common.rs b/tee-worker/core/tls-websocket-server/src/tls_common.rs new file mode 100644 index 0000000000..c2061abf87 --- /dev/null +++ b/tee-worker/core/tls-websocket-server/src/tls_common.rs @@ -0,0 +1,70 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +#[cfg(all(not(feature = "std"), feature = "sgx"))] +use crate::sgx_reexport_prelude::*; + +use crate::{error::WebSocketError, WebSocketResult}; +use rustls::NoClientAuth; +use std::{io::BufReader, string::ToString, sync::Arc, vec, vec::Vec}; + +pub fn make_config(cert: &str, key: &str) -> WebSocketResult> { + let mut config = rustls::ServerConfig::new(NoClientAuth::new()); + + let certs = load_certs(cert)?; + let privkey = load_private_key(key)?; + + config + .set_single_cert_with_ocsp_and_sct(certs, privkey, vec![], vec![]) + .expect("Invalid key der"); + + Ok(Arc::new(config)) +} + +fn load_certs(pem_content: &str) -> WebSocketResult> { + let mut reader = BufReader::new(pem_content.as_bytes()); + rustls::internal::pemfile::certs(&mut reader) + .map_err(|_| WebSocketError::InvalidCertificate("Failed to parse certificate".to_string())) +} + +fn load_private_key(pem_content: &str) -> WebSocketResult { + let rsa_keys = { + let mut reader = BufReader::new(pem_content.as_bytes()); + + rustls::internal::pemfile::rsa_private_keys(&mut reader).map_err(|_| { + WebSocketError::InvalidPrivateKey("Failed to parse RSA private key".to_string()) + })? + }; + + let pkcs8_keys = { + let mut reader = BufReader::new(pem_content.as_bytes()); + rustls::internal::pemfile::pkcs8_private_keys(&mut reader).map_err(|_| { + WebSocketError::InvalidPrivateKey( + "Invalid PKCS8 private key (encrypted keys are not supported)".to_string(), + ) + })? + }; + + // prefer to load pkcs8 keys + if !pkcs8_keys.is_empty() { + Ok(pkcs8_keys[0].clone()) + } else if !rsa_keys.is_empty() { + Ok(rsa_keys[0].clone()) + } else { + Err(WebSocketError::InvalidPrivateKey("No viable private keys were given".to_string())) + } +} diff --git a/tee-worker/core/tls-websocket-server/src/ws_server.rs b/tee-worker/core/tls-websocket-server/src/ws_server.rs new file mode 100644 index 0000000000..cacac43b33 --- /dev/null +++ b/tee-worker/core/tls-websocket-server/src/ws_server.rs @@ -0,0 +1,518 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +#[cfg(all(not(feature = "std"), feature = "sgx"))] +use crate::sgx_reexport_prelude::*; + +#[cfg(feature = "sgx")] +use std::sync::{SgxMutex as Mutex, SgxRwLock as RwLock}; + +#[cfg(feature = "std")] +use std::sync::{Mutex, RwLock}; + +use crate::{ + config_provider::ProvideServerConfig, + connection::TungsteniteWsConnection, + connection_id_generator::GenerateConnectionId, + error::{WebSocketError, WebSocketResult}, + ConnectionIdGenerator, ConnectionToken, WebSocketConnection, WebSocketMessageHandler, + WebSocketResponder, WebSocketServer, +}; +use log::*; +use mio::{ + event::{Event, Evented}, + net::TcpListener, + Poll, +}; +use mio_extras::channel::{channel, Receiver, Sender}; +use net::SocketAddr; +use rustls::ServerConfig; +use std::{collections::HashMap, format, net, string::String, sync::Arc}; + +// Default tokens for the server. +pub(crate) const NEW_CONNECTIONS_LISTENER: mio::Token = mio::Token(0); +pub(crate) const SERVER_SIGNAL_TOKEN: mio::Token = mio::Token(1); + +/// Secure web-socket server implementation using the Tungstenite library. +pub struct TungsteniteWsServer { + ws_address: String, + config_provider: Arc, + connection_handler: Arc, + id_generator: ConnectionIdGenerator, + connections: RwLock>>, + is_running: RwLock, + signal_sender: Mutex>>, +} + +impl TungsteniteWsServer +where + ConfigProvider: ProvideServerConfig, + Handler: WebSocketMessageHandler, +{ + pub fn new( + ws_address: String, + config_provider: Arc, + connection_handler: Arc, + ) -> Self { + TungsteniteWsServer { + ws_address, + config_provider, + connection_handler, + id_generator: ConnectionIdGenerator::default(), + connections: Default::default(), + is_running: Default::default(), + signal_sender: Default::default(), + } + } + + fn accept_connection( + &self, + poll: &mut Poll, + tcp_listener: &TcpListener, + tls_config: Arc, + ) -> WebSocketResult<()> { + let (socket, addr) = tcp_listener.accept()?; + + debug!("Accepting new connection from {:?}", addr); + + let tls_session = rustls::ServerSession::new(&tls_config); + let connection_id = self.id_generator.next_id()?; + let token = mio::Token(connection_id); + trace!("New connection has token {:?}", token); + + let mut web_socket_connection = TungsteniteWsConnection::new( + socket, + tls_session, + token, + self.connection_handler.clone(), + )?; + + trace!("Web-socket connection created"); + web_socket_connection.register(poll)?; + + let mut connections_lock = + self.connections.write().map_err(|_| WebSocketError::LockPoisoning)?; + connections_lock.insert(token, web_socket_connection); + + debug!("Accepted connection, {} active connections", connections_lock.len()); + Ok(()) + } + + fn connection_event(&self, poll: &mut mio::Poll, event: &Event) -> WebSocketResult<()> { + let token = event.token(); + + let mut connections_lock = + self.connections.write().map_err(|_| WebSocketError::LockPoisoning)?; + + if let Some(connection) = connections_lock.get_mut(&token) { + connection.on_ready(poll, event)?; + + if connection.is_closed() { + trace!("Connection {:?} is closed, removing", token); + connections_lock.remove(&token); + trace!( + "Closed {:?}, {} active connections remaining", + token, + connections_lock.len() + ); + } + } + + Ok(()) + } + + /// Send a message response to a connection. + /// Make sure this is called inside the event loop, otherwise dead-locks are possible. + fn write_message_to_connection( + &self, + message: String, + connection_token: ConnectionToken, + ) -> WebSocketResult<()> { + let mut connections_lock = + self.connections.write().map_err(|_| WebSocketError::LockPoisoning)?; + let connection = connections_lock + .get_mut(&connection_token.into()) + .ok_or_else(|| WebSocketError::InvalidConnection(connection_token.0))?; + connection.write_message(message) + } + + fn handle_server_signal( + &self, + poll: &mut mio::Poll, + event: &Event, + signal_receiver: &mut Receiver, + ) -> WebSocketResult { + let signal = signal_receiver.try_recv()?; + let mut do_shutdown = false; + + match signal { + ServerSignal::ShutDown => { + do_shutdown = true; + }, + ServerSignal::SendResponse(message, connection_token) => { + if let Err(e) = self.write_message_to_connection(message, connection_token) { + error!("Failed to send web-socket response: {:?}", e); + } + }, + } + + signal_receiver.reregister( + poll, + event.token(), + mio::Ready::readable(), + mio::PollOpt::level(), + )?; + + Ok(do_shutdown) + } + + fn register_server_signal_sender(&self, sender: Sender) -> WebSocketResult<()> { + let mut sender_lock = + self.signal_sender.lock().map_err(|_| WebSocketError::LockPoisoning)?; + *sender_lock = Some(sender); + Ok(()) + } + + fn send_server_signal(&self, server_signal: ServerSignal) -> WebSocketResult<()> { + match self.signal_sender.lock().map_err(|_| WebSocketError::LockPoisoning)?.as_ref() { + None => { + warn!( + "Signal sender has not been initialized, cannot send web-socket server signal" + ); + }, + Some(signal_sender) => { + signal_sender + .send(server_signal) + .map_err(|e| WebSocketError::Other(format!("{:?}", e).into()))?; + }, + } + + Ok(()) + } +} + +impl WebSocketServer for TungsteniteWsServer +where + ConfigProvider: ProvideServerConfig, + Handler: WebSocketMessageHandler, +{ + type Connection = TungsteniteWsConnection; + + fn run(&self) -> WebSocketResult<()> { + debug!("Running tungstenite web socket server on {}", self.ws_address); + + let socket_addr: SocketAddr = + self.ws_address.parse().map_err(WebSocketError::InvalidWsAddress)?; + + let config = self.config_provider.get_config()?; + + let (server_signal_sender, mut signal_receiver) = channel::(); + self.register_server_signal_sender(server_signal_sender)?; + + let tcp_listener = net::TcpListener::bind(socket_addr).expect("Could not listen on port"); + let tcp_listener = + mio::net::TcpListener::from_std(tcp_listener).map_err(WebSocketError::TcpBindError)?; + let mut poll = Poll::new()?; + poll.register( + &tcp_listener, + NEW_CONNECTIONS_LISTENER, + mio::Ready::readable(), + mio::PollOpt::level(), + )?; + + poll.register( + &signal_receiver, + SERVER_SIGNAL_TOKEN, + mio::Ready::readable(), + mio::PollOpt::level(), + )?; + + let mut events = mio::Events::with_capacity(2048); + + *self.is_running.write().map_err(|_| WebSocketError::LockPoisoning)? = true; + + // Run the event loop. + 'outer_event_loop: loop { + let num_events = poll.poll(&mut events, None)?; + debug!("Number of readiness events: {}", num_events); + + for event in events.iter() { + match event.token() { + NEW_CONNECTIONS_LISTENER => { + trace!("Received new connection event"); + if let Err(e) = + self.accept_connection(&mut poll, &tcp_listener, config.clone()) + { + error!("Failed to accept new web-socket connection: {:?}", e); + } + }, + SERVER_SIGNAL_TOKEN => { + trace!("Received server signal event"); + if self.handle_server_signal(&mut poll, &event, &mut signal_receiver)? { + break 'outer_event_loop + } + }, + _ => { + trace!("Connection (token {:?}) activity event", event.token()); + if let Err(e) = self.connection_event(&mut poll, &event) { + error!("Failed to process connection event: {:?}", e); + } + }, + } + } + } + + info!("Web-socket server has shut down"); + Ok(()) + } + + fn is_running(&self) -> WebSocketResult { + Ok(*self.is_running.read().map_err(|_| WebSocketError::LockPoisoning)?) + } + + fn shut_down(&self) -> WebSocketResult<()> { + info!("Shutdown request of web-socket server detected, shutting down.."); + self.send_server_signal(ServerSignal::ShutDown) + } +} + +impl WebSocketResponder for TungsteniteWsServer +where + ConfigProvider: ProvideServerConfig, + Handler: WebSocketMessageHandler, +{ + fn send_message( + &self, + connection_token: ConnectionToken, + message: String, + ) -> WebSocketResult<()> { + self.send_server_signal(ServerSignal::SendResponse(message, connection_token)) + } +} + +/// Internal server signal enum. +enum ServerSignal { + ShutDown, + SendResponse(String, ConnectionToken), +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::test::{ + fixtures::{no_cert_verifier::NoCertVerifier, test_server::create_server}, + mocks::web_socket_handler_mock::WebSocketHandlerMock, + }; + use rustls::ClientConfig; + use std::{net::TcpStream, thread, time::Duration}; + use tungstenite::{ + client_tls_with_config, stream::MaybeTlsStream, Connector, Message, WebSocket, + }; + use url::Url; + + #[test] + fn server_handles_multiple_connections() { + let _ = env_logger::builder().is_test(true).try_init(); + + let expected_answer = "websocket server response bidibibup".to_string(); + let port: u16 = 21777; + const NUMBER_OF_CONNECTIONS: usize = 100; + + let (server, handler) = create_server(vec![expected_answer.clone()], port); + + let server_clone = server.clone(); + let server_join_handle = thread::spawn(move || server_clone.run()); + + // Wait until server is up. + while !server.is_running().unwrap() { + thread::sleep(std::time::Duration::from_millis(50)); + } + + // Spawn multiple clients that connect to the server simultaneously and send a message. + let client_handles: Vec<_> = (0..NUMBER_OF_CONNECTIONS) + .map(|_| { + let expected_answer_clone = expected_answer.clone(); + + thread::sleep(Duration::from_millis(5)); + + thread::spawn(move || { + let mut socket = connect_tls_client(get_server_addr(port).as_str()); + + socket + .write_message(Message::Text("Hello WebSocket".into())) + .expect("client write message to be successful"); + + assert_eq!( + Message::Text(expected_answer_clone), + socket.read_message().unwrap() + ); + + thread::sleep(Duration::from_millis(2)); + + socket + .write_message(Message::Text("Second message".into())) + .expect("client write message to be successful"); + + thread::sleep(Duration::from_millis(2)); + + socket.close(None).unwrap(); + socket.write_pending().unwrap(); + }) + }) + .collect(); + + for handle in client_handles.into_iter() { + handle.join().expect("client handle to be joined"); + } + + server.shut_down().unwrap(); + + let server_shutdown_result = + server_join_handle.join().expect("Couldn't join on the associated thread"); + if let Err(e) = server_shutdown_result { + panic!("Test failed, web-socket returned error: {:?}", e); + } + + assert_eq!(2 * NUMBER_OF_CONNECTIONS, handler.get_handled_messages().len()); + } + + #[test] + fn server_closes_connection_if_client_does_not_wait_for_reply() { + let _ = env_logger::builder().is_test(true).try_init(); + + let expected_answer = "websocket server response".to_string(); + let port: u16 = 21778; + + let (server, handler) = create_server(vec![expected_answer.clone()], port); + + let server_clone = server.clone(); + let server_join_handle = thread::spawn(move || server_clone.run()); + + // Wait until server is up. + while !server.is_running().unwrap() { + thread::sleep(std::time::Duration::from_millis(50)); + } + + let client_join_handle = thread::spawn(move || { + let mut socket = connect_tls_client(get_server_addr(port).as_str()); + socket + .write_message(Message::Text("First request".into())) + .expect("client write message to be successful"); + + // We never read, just send a message and close the connection, despite the server + // trying to send a reply (which will fail). + socket.close(None).unwrap(); + socket.write_pending().unwrap(); + }); + + client_join_handle.join().unwrap(); + server.shut_down().unwrap(); + server_join_handle.join().unwrap().unwrap(); + + assert_eq!(1, handler.get_handled_messages().len()); + } + + #[test] + fn server_sends_update_message_to_client() { + let _ = env_logger::builder().is_test(true).try_init(); + + let expected_answer = "first response".to_string(); + let port: u16 = 21779; + let (server, handler) = create_server(vec![expected_answer.clone()], port); + + let server_clone = server.clone(); + let server_join_handle = thread::spawn(move || server_clone.run()); + + // Wait until server is up. + while !server.is_running().unwrap() { + thread::sleep(std::time::Duration::from_millis(50)); + } + + let update_message = "Message update".to_string(); + let update_message_clone = update_message.clone(); + + let client_join_handle = thread::spawn(move || { + let mut socket = connect_tls_client(get_server_addr(port).as_str()); + socket + .write_message(Message::Text("First request".into())) + .expect("client write message to be successful"); + + assert_eq!(Message::Text(expected_answer), socket.read_message().unwrap()); + assert_eq!(Message::Text(update_message_clone), socket.read_message().unwrap()); + }); + + let connection_token = poll_handler_for_first_connection(handler.as_ref()); + + // Send reply to a wrong connection token. Succeeds, because error is caught in the event loop + // and not the `send_message` method itself. + assert!(server + .send_message( + ConnectionToken(connection_token.0 + 1), + "wont get to the client".to_string() + ) + .is_ok()); + + // Send reply to the correct connection token. + server.send_message(connection_token, update_message).unwrap(); + + client_join_handle.join().unwrap(); + server.shut_down().unwrap(); + server_join_handle.join().unwrap().unwrap(); + + assert_eq!(1, handler.get_handled_messages().len()); + } + + // Ignored because it does not directly test any of our own components. + // It was used to test the behavior of the tungstenite client configuration with certificates. + #[test] + #[ignore] + fn client_test() { + let mut socket = connect_tls_client("ws.ifelse.io:443"); + + socket + .write_message(Message::Text("Hello WebSocket".into())) + .expect("client write message to be successful"); + } + + fn poll_handler_for_first_connection(handler: &WebSocketHandlerMock) -> ConnectionToken { + loop { + match handler.get_handled_messages().first() { + None => thread::sleep(Duration::from_millis(5)), + Some(m) => return m.0, + } + } + } + + fn get_server_addr(port: u16) -> String { + format!("localhost:{}", port) + } + + fn connect_tls_client(server_addr: &str) -> WebSocket> { + let ws_server_url = Url::parse(format!("wss://{}", server_addr).as_str()).unwrap(); + + let mut config = ClientConfig::new(); + config.dangerous().set_certificate_verifier(Arc::new(NoCertVerifier {})); + let connector = Connector::Rustls(Arc::new(config)); + let stream = TcpStream::connect(server_addr).unwrap(); + + let (socket, _response) = + client_tls_with_config(ws_server_url, stream, None, Some(connector)) + .expect("Can't connect"); + + socket + } +} diff --git a/tee-worker/docker/README.md b/tee-worker/docker/README.md new file mode 100644 index 0000000000..d19112d656 --- /dev/null +++ b/tee-worker/docker/README.md @@ -0,0 +1,114 @@ +# How to run the multi-validateer docker setup + +## Prerequisite + +* Make sure you have installed Docker (version > `1.25.0`) with [Docker Compose](https://docs.docker.com/compose/install/). On Windows, this can be Docker Desktop with WSL 2 integration. +* In case you also build the worker directly, without docker (e.g. on a dev machine, running `make`), you should run `make clean` before running the docker build. Otherwise, it can occasionally lead to build errors. +* The node image version that is loaded in the `docker-compose.yml`, (e.g. `image: "integritee/integritee-node-dev:1.0.11"`) needs to be compatible with the worker you're trying to build. + +## Building the Docker containers + +Run +``` +COMPOSE_DOCKER_CLI_BUILD=1 DOCKER_BUILDKIT=1 docker compose build +``` +in this folder to build the worker image. This will build the worker from source and tag it in an image called `integritee-worker:dev`. + +## Running the docker setup + +``` +docker compose up +``` +Starts all services (node and workers), using the `integritee-worker:dev` images you've built in the previous step. + +## Run the demos + +### Demo indirect invocation (M6) +Build +``` +COMPOSE_DOCKER_CLI_BUILD=1 DOCKER_BUILDKIT=1 docker compose -f docker-compose.yml -f demo-indirect-invocation.yml build --build-arg WORKER_MODE_ARG=offchain-worker +``` +Run +``` +docker compose -f docker-compose.yml -f demo-indirect-invocation.yml up demo-indirect-invocation --exit-code-from demo-indirect-invocation +``` +### Demo direct call (M8) + +Build +``` +COMPOSE_DOCKER_CLI_BUILD=1 DOCKER_BUILDKIT=1 docker compose -f docker-compose.yml -f demo-direct-call.yml build --build-arg WORKER_MODE_ARG=sidechain +``` +Run +``` +docker compose -f docker-compose.yml -f demo-direct-call.yml up demo-direct-call --exit-code-from demo-direct-call +``` + +### Demo sidechain +Build +``` +COMPOSE_DOCKER_CLI_BUILD=1 DOCKER_BUILDKIT=1 docker compose -f docker-compose.yml -f demo-sidechain.yml build --build-arg WORKER_MODE_ARG=sidechain +``` +Run +``` +docker compose -f docker-compose.yml -f demo-sidechain.yml up demo-sidechain --exit-code-from demo-sidechain +``` + +### Demo Teeracle +Build +``` +COMPOSE_DOCKER_CLI_BUILD=1 DOCKER_BUILDKIT=1 docker compose -f docker-compose.yml -f demo-teeracle.yml build --build-arg WORKER_MODE_ARG=teeracle +``` +Run +``` +docker compose -f docker-compose.yml -f demo-teeracle.yml up demo-teeracle --exit-code-from demo-teeracle +``` + + +## Run the benchmarks +Build with +``` +COMPOSE_DOCKER_CLI_BUILD=1 DOCKER_BUILDKIT=1 docker compose -f docker-compose.yml -f sidechain-benchmark.yml build +``` +and then run with +``` +docker compose -f docker-compose.yml -f sidechain-benchmark.yml up sidechain-benchmark --exit-code-from sidechain-benchmark +``` + +## Run the fork simulator +The fork simulation uses `pumba` which in turn uses the Linux traffic control (TC). This is only available on Linux hosts, not on Windows with WSL unfortunately. +Build the docker compose setup with +``` +COMPOSE_DOCKER_CLI_BUILD=1 DOCKER_BUILDKIT=1 docker compose -f docker-compose.yml -f fork-inducer.yml -f demo-sidechain.yml build --build-arg WORKER_MODE_ARG=sidechain +``` + +This requires the docker BuildKit (docker version >= 18.09) and support for it in docker compose (version >= 1.25.0) + +Run the 2-worker setup with a fork inducer (pumba) that delays the traffic on worker 2 +``` +docker compose -f docker-compose.yml -f fork-inducer.yml -f integration-test.yml up --exit-code-from demo-sidechain +``` + +This should show that the integration test fails, because we had an unhandled fork in the sidechain. Clean up the containers after each run with: +``` +docker compose -f docker-compose.yml -f fork-inducer.yml -f demo-sidechain.yml down +``` + +We need these different compose files to separate the services that we're using. E.g. we want the integration test and fork simulator to be optional. The same could be solved using `profiles` - but that requires a more up-to-date version of `docker compose`. + +## FAQ +### What do I have to do to stop everything properly? +With `Ctrl-C` you stop the containers and with `docker compose down` you clean up/remove the containers. Note that `docker compose down` will also remove any logs docker has saved, since it will remove all the container context. + +### What do I have to do if I make changes to the code? +You need to re-build the worker image, using `docker compose build`. + +### How can I change the log level? +You can change the environment variable `RUST_LOG=` in the `docker-compose.yml` for each worker individually. + +### The log from the node are quite a nuisance. Why are they all together. +You can suppress the log output for a container by setting the logging driver. This can be set to either `none` (completely disables all logs), or `local` (docker will record the logs, depending on your docker compose version, it will also log to `stdout`) in the `docker-compose.yml`: +``` +logging: + driver: local +``` +Mind the indent. Explanations for all the logging drivers in `docker compose` can be found [here](https://docs.docker.com/config/containers/logging/local/). \ No newline at end of file diff --git a/tee-worker/docker/demo-direct-call.yml b/tee-worker/docker/demo-direct-call.yml new file mode 100644 index 0000000000..72957e5f14 --- /dev/null +++ b/tee-worker/docker/demo-direct-call.yml @@ -0,0 +1,18 @@ +services: + demo-direct-call: + image: integritee-cli:dev + container_name: integritee-direct-call-demo + build: + context: .. + dockerfile: build.Dockerfile + target: deployed-client + depends_on: ['integritee-node', 'integritee-worker-1', 'integritee-worker-2'] + networks: + - integritee-test-network + entrypoint: "dockerize -wait http://integritee-worker-2:4646/is_initialized -timeout 600s + /usr/local/worker-cli/demo_direct_call_2_workers.sh -p 9912 -u ws://integritee-node + -V wss://integritee-worker-1 -A 2011 -W wss://integritee-worker-2 -B 2012 -C /usr/local/bin/integritee-cli 2>&1" + restart: "no" +networks: + integritee-test-network: + driver: bridge \ No newline at end of file diff --git a/tee-worker/docker/demo-indirect-invocation.yml b/tee-worker/docker/demo-indirect-invocation.yml new file mode 100644 index 0000000000..a2fedbb939 --- /dev/null +++ b/tee-worker/docker/demo-indirect-invocation.yml @@ -0,0 +1,20 @@ +services: + demo-indirect-invocation: + image: integritee-cli:dev + container_name: integritee-indirect-invocation-demo + build: + context: .. + dockerfile: build.Dockerfile + target: deployed-client + depends_on: ['integritee-node', 'integritee-worker-1', 'integritee-worker-2'] + environment: + - RUST_LOG=warn,ws=warn,itc_rpc_client=warn + networks: + - integritee-test-network + entrypoint: "dockerize -wait http://integritee-worker-2:4646/is_initialized -timeout 600s + /usr/local/worker-cli/demo_indirect_invocation.sh -p 9912 -u ws://integritee-node + -V wss://integritee-worker-1 -A 2011 -W wss://integritee-worker-2 -B 2012 -C /usr/local/bin/integritee-cli 2>&1" + restart: "no" +networks: + integritee-test-network: + driver: bridge \ No newline at end of file diff --git a/tee-worker/docker/demo-sidechain.yml b/tee-worker/docker/demo-sidechain.yml new file mode 100644 index 0000000000..0f9f8e0005 --- /dev/null +++ b/tee-worker/docker/demo-sidechain.yml @@ -0,0 +1,18 @@ +services: + demo-sidechain: + image: integritee-cli:dev + container_name: integritee-sidechain-demo + build: + context: .. + dockerfile: build.Dockerfile + target: deployed-client + depends_on: ['integritee-node', 'integritee-worker-1', 'integritee-worker-2'] + networks: + - integritee-test-network + entrypoint: "dockerize -wait http://integritee-worker-2:4646/is_initialized -timeout 600s + /usr/local/worker-cli/demo_sidechain.sh -p 9912 -A 2011 -B 2012 -u ws://integritee-node + -V wss://integritee-worker-1 -W wss://integritee-worker-2 -C /usr/local/bin/integritee-cli 2>&1" + restart: "no" +networks: + integritee-test-network: + driver: bridge \ No newline at end of file diff --git a/tee-worker/docker/demo-smart-contract.yml b/tee-worker/docker/demo-smart-contract.yml new file mode 100644 index 0000000000..ecf3e9d120 --- /dev/null +++ b/tee-worker/docker/demo-smart-contract.yml @@ -0,0 +1,20 @@ +services: + demo-smart-contract: + image: integritee-cli:dev + container_name: integritee-smart-contract-demo + build: + context: .. + dockerfile: build.Dockerfile + target: deployed-client + depends_on: ['integritee-node', 'integritee-worker-1', 'integritee-worker-2'] + environment: + - RUST_LOG=warn,ws=warn,itc_rpc_client=warn + networks: + - integritee-test-network + entrypoint: "dockerize -wait http://integritee-worker-2:4646/is_initialized -timeout 600s + /usr/local/worker-cli/demo_smart_contract.sh -p 9912 -u ws://integritee-node + -V wss://integritee-worker-1 -A 2011 -C /usr/local/bin/integritee-cli 2>&1" + restart: "no" +networks: + integritee-test-network: + driver: bridge diff --git a/tee-worker/docker/demo-teeracle.yml b/tee-worker/docker/demo-teeracle.yml new file mode 100644 index 0000000000..d905b21d4a --- /dev/null +++ b/tee-worker/docker/demo-teeracle.yml @@ -0,0 +1,47 @@ +# Teeracle Demo Setup +# +# The demo is parameterized with the interval that the teeracle uses to query its sources. +# Set the `TEERACLE_INTERVAL_SECONDS` variable when invoking, e.g. `TEERACLE_INTERVAL_SECONDS=4 docker compose -f docker-compose.yml -f demo-teeracle.yml up --exit-code-from demo-teeracle` +# This setup requires an API key for CoinMarketCap +# Add the API key to the environment variable `COINMARKETCAP_KEY`, with `export COINMARKETCAP_KEY=` +services: + integritee-teeracle-worker: + image: integritee-worker:dev + container_name: integritee-teeracle-worker + build: + context: .. + dockerfile: build.Dockerfile + target: deployed-worker + depends_on: [ 'integritee-node' ] + environment: + - RUST_LOG=warn,ws=warn,sp_io=warn,substrate_api_client=warn,jsonrpsee_ws_client=warn,jsonrpsee_ws_server=warn,enclave_runtime=warn,integritee_service=info,integritee_service::teeracle=debug,ita_stf=warn,ita_exchange_oracle=debug + - COINMARKETCAP_KEY + networks: + - integritee-test-network + entrypoint: "dockerize -wait tcp://integritee-node:9912 -timeout 600s + /usr/local/bin/integritee-service --clean-reset --ws-external -M integritee-teeracle-worker -T wss://integritee-teeracle-worker + -u ws://integritee-node -U ws://integritee-teeracle-worker -P 2011 -w 2101 -p 9912 -h 4645 + run --dev --skip-ra --teeracle-interval ${TEERACLE_INTERVAL_SECONDS}s" + restart: always + demo-teeracle: + image: integritee-cli:dev + container_name: integritee-teeracle-demo + build: + context: .. + dockerfile: build.Dockerfile + target: deployed-client + depends_on: ['integritee-node', 'integritee-teeracle-worker'] + environment: + - RUST_LOG=warn,sp_io=warn,integritee_cli::exchange_oracle=debug + networks: + - integritee-test-network + entrypoint: "dockerize -wait http://integritee-teeracle-worker:4645/is_initialized -timeout 600s + /usr/local/worker-cli/demo_teeracle_whitelist.sh + -u ws://integritee-node -p 9912 + -V wss://integritee-teeracle-worker -P 2011 + -d 21 -i ${TEERACLE_INTERVAL_SECONDS} + -C /usr/local/bin/integritee-cli 2>&1" + restart: "no" +networks: + integritee-test-network: + driver: bridge \ No newline at end of file diff --git a/tee-worker/docker/docker-compose.yml b/tee-worker/docker/docker-compose.yml new file mode 100644 index 0000000000..484d1728f8 --- /dev/null +++ b/tee-worker/docker/docker-compose.yml @@ -0,0 +1,141 @@ + + +services: + relaychain-alice: + image: docker_relaychain-alice:latest + networks: + - integritee-test-network + ports: + - 9946:9944 + - 9936:9933 + - 30336:30333 + volumes: + - relaychain-alice:/data + build: + context: litentry + dockerfile: relaychain.Dockerfile + command: + - --base-path=/data + - --chain=/app/rococo-local.json + - --validator + - --ws-external + - --rpc-external + - --rpc-cors=all + - --name=alice + - --alice + - --rpc-methods=unsafe + - --execution=wasm + environment: + RUST_LOG: parachain::candidate-backing=trace,parachain::candidate-selection=trace,parachain::pvf=trace,parachain::collator-protocol=trace,parachain::provisioner=trace + ulimits: + &a1 + nofile: + soft: 65536 + hard: 65536 + relaychain-bob: + image: docker_relaychain-bob:latest + networks: + - integritee-test-network + ports: + - 9947:9944 + - 9937:9933 + - 30337:30333 + volumes: + - relaychain-bob:/data + build: + context: litentry + dockerfile: relaychain.Dockerfile + command: + - --base-path=/data + - --chain=/app/rococo-local.json + - --validator + - --ws-external + - --rpc-external + - --rpc-cors=all + - --name=bob + - --bob + - --rpc-methods=unsafe + - --execution=wasm + environment: + RUST_LOG: parachain::candidate-backing=trace,parachain::candidate-selection=trace,parachain::pvf=trace,parachain::collator-protocol=trace,parachain::provisioner=trace + ulimits: *a1 + # this is actually litentry parachain, keep the integritee name so that we don't have change + # the service name everywhere in other yml files + integritee-node: + image: docker_integritee-node:latest + container_name: integritee-node + networks: + - integritee-test-network + ports: + # integritee uses 9912 as ws port (why?) + - 9944:9912 + - 9933:9933 + - 30333:30333 + volumes: + - parachain-2106-0:/data + build: + context: litentry + dockerfile: parachain-2106.Dockerfile + depends_on: ['relaychain-alice', 'relaychain-bob'] + command: + - --base-path=/data + - --chain=/app/rococo-dev-2106.json + - --ws-external + - --rpc-external + - --rpc-cors=all + - --name=parachain-2106-0 + - --ws-port=9912 + - --collator + - --rpc-methods=unsafe + - --force-authoring + - --execution=wasm + - --alice + - --node-key=e998e728d8bf5bff6670c5e2b20455f6de1742b7ca564057680c9781cf037dd1 + - --listen-addr=/ip4/0.0.0.0/tcp/30333 + - -- + - --chain=/app/rococo-local.json + - --execution=wasm + environment: + RUST_LOG: sc_basic_authorship=trace,cumulus-consensus=trace,cumulus-collator=trace,collator_protocol=trace,collation_generation=trace,aura=debug + ulimits: *a1 + integritee-worker-1: + image: integritee-worker:dev + container_name: integritee-worker-1 + build: + context: .. + dockerfile: build.Dockerfile + target: deployed-worker + depends_on: ['integritee-node'] + environment: + - RUST_LOG=warn,ws=warn,sp_io=warn,substrate_api_client=warn,jsonrpsee_ws_client=warn,jsonrpsee_ws_server=warn,enclave_runtime=warn,integritee_service=warn,ita_stf=warn,enclave_runtime::top_pool_execution=info + networks: + - integritee-test-network + entrypoint: "dockerize -wait tcp://integritee-node:9912 -timeout 600s + /usr/local/bin/integritee-service --clean-reset --ws-external -M integritee-worker-1 -T wss://integritee-worker-1 + -u ws://integritee-node -U ws://integritee-worker-1 -P 2011 -w 2101 -p 9912 -h 4645 + run --dev --skip-ra" + restart: "no" + integritee-worker-2: + image: integritee-worker:dev + container_name: integritee-worker-2 + build: + context: .. + dockerfile: build.Dockerfile + target: deployed-worker + depends_on: ['integritee-node', 'integritee-worker-1'] + environment: + - RUST_LOG=warn,ws=warn,sp_io=warn,substrate_api_client=warn,jsonrpsee_ws_client=warn,jsonrpsee_ws_server=warn,enclave_runtime=warn,integritee_service=warn,ita_stf=warn + networks: + - integritee-test-network + entrypoint: "dockerize -wait http://integritee-worker-1:4645/is_initialized -timeout 600s + /usr/local/bin/integritee-service --clean-reset --ws-external -M integritee-worker-2 -T wss://integritee-worker-2 + -u ws://integritee-node -U ws://integritee-worker-2 -P 2012 -w 2102 -p 9912 -h 4646 + run --dev --skip-ra --request-state" + restart: "no" +volumes: + ? relaychain-alice + ? relaychain-bob + ? parachain-2106-0 +networks: + integritee-test-network: + driver: bridge diff --git a/tee-worker/docker/fork-inducer.yml b/tee-worker/docker/fork-inducer.yml new file mode 100644 index 0000000000..302503c4f6 --- /dev/null +++ b/tee-worker/docker/fork-inducer.yml @@ -0,0 +1,26 @@ +services: + worker-ping: + image: worker-ping:dev + build: + context: . + dockerfile: ping.Dockerfile + depends_on: [ 'integritee-node', 'integritee-worker-1', 'integritee-worker-2' ] + networks: + - integritee-test-network + #entrypoint: "ping integritee-worker-2 | while read pong; do echo \"$$(date): $$pong\"; done" + entrypoint: "ping integritee-worker-2" + pumba-network-delay: + image: integritee-fork-producer:dev + build: + context: . + dockerfile: fork.Dockerfile + depends_on: [ 'integritee-node', 'integritee-worker-1', 'integritee-worker-2' ] + networks: + - integritee-test-network + volumes: + - /var/run/docker.sock:/var/run/docker.sock + entrypoint: "dockerize -wait http://integritee-worker-2:4646/is_initialized -timeout 600s + pumba --interval 3m netem --interface eth0 --duration 30s delay --time 1000 integritee-worker-2" +networks: + integritee-test-network: + driver: bridge \ No newline at end of file diff --git a/tee-worker/docker/fork.Dockerfile b/tee-worker/docker/fork.Dockerfile new file mode 100644 index 0000000000..3a2df5bb85 --- /dev/null +++ b/tee-worker/docker/fork.Dockerfile @@ -0,0 +1,26 @@ +# Copyright 2021 Integritee AG +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +### Build Pumba image with dockerize +################################################## +FROM scratch AS fork-simulator-deployed +LABEL maintainer="zoltan@integritee.network" + +COPY --from=gaiaadm/pumba /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt +COPY --from=gaiaadm/pumba /pumba /usr/local/bin/pumba +COPY --from=powerman/dockerize /usr/local/bin/dockerize /usr/local/bin/dockerize + +ENV PATH "$PATH:/usr/local/bin" + +ENTRYPOINT ["/usr/local/bin/dockerize"] \ No newline at end of file diff --git a/tee-worker/docker/https-test.yml b/tee-worker/docker/https-test.yml new file mode 100644 index 0000000000..a5b33686cc --- /dev/null +++ b/tee-worker/docker/https-test.yml @@ -0,0 +1,18 @@ +services: + https-test: + image: integritee-cli:dev + container_name: litentry-https-test + build: + context: .. + dockerfile: build.Dockerfile + target: deployed-client + depends_on: ['integritee-node', 'integritee-worker-1', 'integritee-worker-2'] + networks: + - integritee-test-network + entrypoint: "dockerize -wait http://integritee-worker-2:4646/is_initialized -timeout 600s + /usr/local/worker-cli/demo_https_test.sh -p 9912 -u ws://integritee-node + -V wss://integritee-worker-1 -A 2011 -W wss://integritee-worker-2 -B 2012 -C /usr/local/bin/integritee-cli 2>&1" + restart: "no" +networks: + integritee-test-network: + driver: bridge \ No newline at end of file diff --git a/tee-worker/docker/litentry-parachain.build.yml b/tee-worker/docker/litentry-parachain.build.yml new file mode 100644 index 0000000000..0fc25bf786 --- /dev/null +++ b/tee-worker/docker/litentry-parachain.build.yml @@ -0,0 +1,106 @@ +version: "3.7" +services: + relaychain-alice: + image: docker_relaychain-alice:latest + networks: + - integritee-test-network + ports: + - 9946:9944 + - 9936:9933 + - 30336:30333 + volumes: + - relaychain-alice:/data + build: + context: litentry + dockerfile: relaychain.Dockerfile + command: + - --base-path=/data + - --chain=/app/rococo-local.json + - --validator + - --ws-external + - --rpc-external + - --rpc-cors=all + - --name=alice + - --alice + - --rpc-methods=unsafe + - --execution=wasm + environment: + RUST_LOG: parachain::candidate-backing=trace,parachain::candidate-selection=trace,parachain::pvf=trace,parachain::collator-protocol=trace,parachain::provisioner=trace + ulimits: + &a1 + nofile: + soft: 65536 + hard: 65536 + relaychain-bob: + image: docker_relaychain-bob:latest + networks: + - integritee-test-network + ports: + - 9947:9944 + - 9937:9933 + - 30337:30333 + volumes: + - relaychain-bob:/data + build: + context: litentry + dockerfile: relaychain.Dockerfile + command: + - --base-path=/data + - --chain=/app/rococo-local.json + - --validator + - --ws-external + - --rpc-external + - --rpc-cors=all + - --name=bob + - --bob + - --rpc-methods=unsafe + - --execution=wasm + environment: + RUST_LOG: parachain::candidate-backing=trace,parachain::candidate-selection=trace,parachain::pvf=trace,parachain::collator-protocol=trace,parachain::provisioner=trace + ulimits: *a1 + # this is actually litentry parachain, keep the integritee name so that we don't have change + # the service name everywhere in other yml files + integritee-node: + image: docker_integritee-node:latest + networks: + - integritee-test-network + ports: + # integritee uses 9912 as ws port (why?) + - 9944:9912 + - 9933:9933 + - 30333:30333 + volumes: + - parachain-2106-0:/data + build: + context: litentry + dockerfile: parachain-2106.Dockerfile + depends_on: ['relaychain-alice', 'relaychain-bob'] + command: + - --base-path=/data + - --chain=/app/rococo-dev-2106.json + - --ws-external + - --rpc-external + - --rpc-cors=all + - --name=parachain-2106-0 + - --ws-port=9912 + - --collator + - --rpc-methods=unsafe + - --force-authoring + - --execution=wasm + - --alice + - --node-key=e998e728d8bf5bff6670c5e2b20455f6de1742b7ca564057680c9781cf037dd1 + - --listen-addr=/ip4/0.0.0.0/tcp/30333 + - -- + - --chain=/app/rococo-local.json + - --execution=wasm + environment: + RUST_LOG: sc_basic_authorship=trace,cumulus-consensus=trace,cumulus-collator=trace,collator_protocol=trace,collation_generation=trace,aura=debug + ulimits: *a1 +volumes: + ? relaychain-alice + ? relaychain-bob + ? parachain-2106-0 +networks: + # to be aligned with other yml files => same network + integritee-test-network: + driver: bridge \ No newline at end of file diff --git a/tee-worker/docker/ping.Dockerfile b/tee-worker/docker/ping.Dockerfile new file mode 100644 index 0000000000..50ea4b7723 --- /dev/null +++ b/tee-worker/docker/ping.Dockerfile @@ -0,0 +1,19 @@ +# Copyright 2021 Integritee AG +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +FROM alpine:latest + +RUN apk add --update iproute2 + +ENTRYPOINT ping \ No newline at end of file diff --git a/tee-worker/docker/sidechain-benchmark.yml b/tee-worker/docker/sidechain-benchmark.yml new file mode 100644 index 0000000000..0bcb7ab0f5 --- /dev/null +++ b/tee-worker/docker/sidechain-benchmark.yml @@ -0,0 +1,18 @@ +services: + sidechain-benchmark: + image: integritee-cli:dev + container_name: integritee-benchmark + build: + context: .. + dockerfile: build.Dockerfile + target: deployed-client + depends_on: ['integritee-node', 'integritee-worker-1', 'integritee-worker-2'] + networks: + - integritee-test-network + entrypoint: "dockerize -wait http://integritee-worker-2:4646/is_initialized -timeout 600s + /usr/local/worker-cli/benchmark.sh -p 9912 -A 2011 -u ws://integritee-node + -V wss://integritee-worker-1 -C /usr/local/bin/integritee-cli 2>&1" + restart: "no" +networks: + integritee-test-network: + driver: bridge \ No newline at end of file diff --git a/tee-worker/docker/ts-tests.yml b/tee-worker/docker/ts-tests.yml new file mode 100644 index 0000000000..7f46d139c5 --- /dev/null +++ b/tee-worker/docker/ts-tests.yml @@ -0,0 +1,19 @@ +services: + ts-tests: + image: integritee-cli:dev + container_name: integritee-ts-tests + volumes: + - ../ts-tests:/ts-tests + build: + context: .. + dockerfile: build.Dockerfile + target: deployed-client + depends_on: ['integritee-node', 'integritee-worker-1', 'integritee-worker-2'] + networks: + - integritee-test-network + entrypoint: "dockerize -wait http://integritee-worker-2:4646/is_initialized -timeout 600s + /usr/local/worker-cli/ts_tests.sh 2>&1" + restart: "no" +networks: + integritee-test-network: + driver: bridge \ No newline at end of file diff --git a/tee-worker/docker/user-shielding-key.yml b/tee-worker/docker/user-shielding-key.yml new file mode 100644 index 0000000000..faebeff5d1 --- /dev/null +++ b/tee-worker/docker/user-shielding-key.yml @@ -0,0 +1,18 @@ +services: + user-shielding-key: + image: integritee-cli:dev + container_name: litentry-user-shielding-key + build: + context: .. + dockerfile: build.Dockerfile + target: deployed-client + depends_on: ['integritee-node', 'integritee-worker-1', 'integritee-worker-2'] + networks: + - integritee-test-network + entrypoint: "dockerize -wait http://integritee-worker-2:4646/is_initialized -timeout 600s + /usr/local/worker-cli/user_shielding_key.sh -p 9912 -u ws://integritee-node + -V wss://integritee-worker-1 -A 2011 -W wss://integritee-worker-2 -B 2012 -C /usr/local/bin/integritee-cli 2>&1" + restart: "no" +networks: + integritee-test-network: + driver: bridge \ No newline at end of file diff --git a/tee-worker/docs/README.md b/tee-worker/docs/README.md new file mode 100644 index 0000000000..c0e42e94fa --- /dev/null +++ b/tee-worker/docs/README.md @@ -0,0 +1,25 @@ +# Knowhow Dump + +This folder contains documents and links that contain some (potentially outdated) information about the worker. +Use with caution, as this is work in progress. Hence, the code is most likely progressing faster than this documentation. + +## Useful links: +### O- / Ecalls +- Ocall Bridge: https://github.com/integritee-network/worker/pull/293 & https://github.com/integritee-network/worker/pull/299 +- Enclave ecalls / ocalls: https://github.com/integritee-network/worker/issues/279 +- Abstract ecalls in enclave: https://github.com/integritee-network/worker/issues/286 +- Abstract ocalls in enclave: https://github.com/integritee-network/worker/issues/279 + +### Sidechain +- Sidechain functionality: https://polkadot.polkassembly.io/post/111 +- Sidechain flow: https://github.com/integritee-network/worker/pull/627 +- Simplified sidechain sequence, of a user call and the STF: https://raw.githubusercontent.com/haerdib/substraTEE_diagramms/main/sidechain-sequence.svg +- Top_pool sequence: https://raw.githubusercontent.com/haerdib/substraTEE_diagramms/main/submit_and_watch_sequence.svg +### Parentchain +- A rough overview of the architecture surrounding the parentchain block import dispatching: https://github.com/integritee-network/worker/pull/530 + +### Runtime +- Enclave runtime: https://github.com/integritee-network/worker/pull/472 + +### Non-worker related graphics +- substrate related graphics: https://github.com/brenzi/substrate-doc diff --git a/tee-worker/docs/diagramms/block_import_sequence.svg b/tee-worker/docs/diagramms/block_import_sequence.svg new file mode 100644 index 0000000000..369cecb4ab --- /dev/null +++ b/tee-worker/docs/diagramms/block_import_sequence.svg @@ -0,0 +1,4 @@ + + + +
For every
sidechain block
For every...
For every
parentchain block
For every...
For every
extrinsic
For every...
For every
shard
For every...
Parentchain BlockImport Queue
pop queue until()
pop queue until()
Light Client
verify block
verify block
import block
import block
! state update
! state update
Node
Validateer / Worker
Validateer / Worker
Substrate Node
Substrate Node
Event: New Finalized Blockget_blocks(last_synced_header)
finalized blocks
finalized blocks
Parentchain BlockImporter
push_to_
import_queue
push_to_...
sync_parentchain(finalized blocks)
last_synced_header
last_synced_header
Sgx Runtime
Sgx Runtime
Sidechain BlockImport Queue
new block
new block
import_block
import_block
Sidechain BlockProducer
create 
sidechain
block
create...
create proposed_sidechain_block
extrinsic
create proposed_sidechain_block...
Top PoolState
calculate state diff
(no state update!)
calculate state diff...
import_parentchain_block(import_until(sidechain block -> parentchain block))Untrusted Listenersubmit_simple_header
Ok()
Ok()
send parentchain extrinsics
send parentchai...
check time
check time
(if_author == self)remove tops (shard, hashes)
Ok()
Ok()
retrieve sidechain blocks
parentchain header
parentchain header
pop until(parentchain header)
blocks
blocks
peek assosciated parentchain header
sidechain blocks
sidechain blocks
latest imported parentchain header
latest imported parentchain header
Sidechain BlockImporter
verify sidechain
block
verify sidechain...
load_state(shard)
load_state(shard)
trigger sidechainblock import
latest parentchain header
latest parentchain header
trusted_calls(shard)
trusted_calls(shard)
get_trusted_calls(shard)Top Pool Execution Loop
intervall trigger
intervall t...
claim_slot
claim_slot
list_shards
shards
shards
exec_aura_on_slot(shards,parentchain header)execute trusted calls(trusted calls)
state_diff, executed hashes
state_diff, executed hashes
sidechain blocks,
extrinsics
sidechain blocks,...
broadcast sidechain block
broadcast sidechai...
Stf::execute(state)
updated state
updated state
Executor
write
(updated state)
write...
execute_indirect_calls_extrinsic(block)
Ok()
Ok()
write(updated_state)
write(updated_state)
For every
parentchain block
For every...
For every
extrinsic
For every...
pop queue until()
pop queue until()
verify block
verify block
import block
import block
! state update
! state update
submit_simple_header
Ok()
Ok()
pop until(parentchain header)
blocks
blocks
latest imported parentchain header
latest imported parentchain header
write
(updated state)
write...
execute_indirect_calls_extrinsic(block)
Ok()
Ok()
import_latest_parentchain_block(parentchain_hedaer)Stf::execute(state)
updated state
updated state
apply_state_update(state, state_diff)+ set_last_block
updated state
updated state
remove invalid tops
Ok()
Ok()
Viewer does not support full SVG 1.1
\ No newline at end of file diff --git a/tee-worker/enclave-runtime/Cargo.lock b/tee-worker/enclave-runtime/Cargo.lock new file mode 100644 index 0000000000..922a4804e1 --- /dev/null +++ b/tee-worker/enclave-runtime/Cargo.lock @@ -0,0 +1,5101 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "Inflector" +version = "0.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe438c63458706e03479442743baae6c88256498e6431708f6dfc520a26515d3" +dependencies = [ + "lazy_static", + "regex 1.7.0", +] + +[[package]] +name = "ac-compose-macros" +version = "0.1.0" +source = "git+https://github.com/scs/substrate-api-client.git?branch=polkadot-v0.9.29#80e6d6daeb1ac8a4ce4968951bb2a4832fb054c0" +dependencies = [ + "ac-primitives", + "log", + "parity-scale-codec", + "sp-application-crypto", + "sp-core", + "sp-runtime", + "sp-std", +] + +[[package]] +name = "ac-node-api" +version = "0.1.0" +source = "git+https://github.com/scs/substrate-api-client.git?branch=polkadot-v0.9.29#80e6d6daeb1ac8a4ce4968951bb2a4832fb054c0" +dependencies = [ + "ac-primitives", + "derive_more", + "frame-metadata 15.0.0 (git+https://github.com/integritee-network/frame-metadata)", + "frame-support", + "frame-system", + "hex 0.4.3", + "log", + "parity-scale-codec", + "scale-info", + "serde 1.0.147", + "serde_json 1.0.89", + "sp-application-crypto", + "sp-core", + "sp-runtime", + "sp-runtime-interface", + "sp-std", +] + +[[package]] +name = "ac-primitives" +version = "0.1.0" +source = "git+https://github.com/scs/substrate-api-client.git?branch=polkadot-v0.9.29#80e6d6daeb1ac8a4ce4968951bb2a4832fb054c0" +dependencies = [ + "hex 0.4.3", + "parity-scale-codec", + "sp-core", + "sp-runtime", + "sp-std", +] + +[[package]] +name = "aes" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "884391ef1066acaa41e766ba8f596341b96e93ce34f9a43e7d24bf0a0eaf0561" +dependencies = [ + "aes-soft", + "aesni", + "cipher", +] + +[[package]] +name = "aes-soft" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be14c7498ea50828a38d0e24a765ed2effe92a705885b57d029cd67d45744072" +dependencies = [ + "cipher", + "opaque-debug 0.3.0", +] + +[[package]] +name = "aesni" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea2e11f5e94c2f7d386164cc2aa1f97823fed6f259e486940a71c174dd01b0ce" +dependencies = [ + "cipher", + "opaque-debug 0.3.0", +] + +[[package]] +name = "ahash" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47" +dependencies = [ + "getrandom 0.2.3", + "once_cell 1.16.0", + "version_check", +] + +[[package]] +name = "aho-corasick" +version = "0.7.10" +source = "git+https://github.com/mesalock-linux/aho-corasick-sgx#7558a97cdf02804f38ec4edd1c0bb0dc2866267f" +dependencies = [ + "memchr 2.2.1", + "sgx_tstd", +] + +[[package]] +name = "aho-corasick" +version = "0.7.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc936419f96fa211c1b9166887b38e5e40b19958e5b895be7c1f93adec7071ac" +dependencies = [ + "memchr 2.5.0", +] + +[[package]] +name = "arrayref" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4c527152e37cf757a3f78aae5a06fbeefdb07ccc535c980a3208ee3060dd544" + +[[package]] +name = "arrayvec" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd9fd44efafa8690358b7408d253adf110036b88f55672a933f01d616ad9b1b9" +dependencies = [ + "nodrop", +] + +[[package]] +name = "arrayvec" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b" + +[[package]] +name = "arrayvec" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8da52d66c7071e2e3fa2a1e5c6d088fec47b593032b254f5e980de8ea54454d6" + +[[package]] +name = "auto_impl" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7862e21c893d65a1650125d157eaeec691439379a1cee17ee49031b79236ada4" +dependencies = [ + "proc-macro-error", + "proc-macro2", + "quote 1.0.21", + "syn 1.0.103", +] + +[[package]] +name = "autocfg" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dde43e75fd43e8a1bf86103336bc699aa8d17ad1be60c76c0bdfd4828e19b78" +dependencies = [ + "autocfg 1.1.0", +] + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "base-x" +version = "0.2.6" +source = "git+https://github.com/whalelephant/base-x-rs?branch=no_std#906c9ac59282ff5a2eec86efd25d50ad9927b147" + +[[package]] +name = "base16ct" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "349a06037c7bf932dd7e7d1f653678b2038b9ad46a74102f1fc7bd7872678cce" + +[[package]] +name = "base64" +version = "0.13.0" +source = "git+https://github.com/mesalock-linux/rust-base64-sgx?tag=sgx_1.1.3#dc7389e10817b078f289386b3b6a852ab6c4c021" +dependencies = [ + "sgx_tstd", +] + +[[package]] +name = "base64" +version = "0.13.0" +source = "git+https://github.com/mesalock-linux/rust-base64-sgx?rev=sgx_1.1.3#dc7389e10817b078f289386b3b6a852ab6c4c021" +dependencies = [ + "sgx_tstd", +] + +[[package]] +name = "base64" +version = "0.13.0" +source = "git+https://github.com/mesalock-linux/rust-base64-sgx#dc7389e10817b078f289386b3b6a852ab6c4c021" +dependencies = [ + "sgx_tstd", +] + +[[package]] +name = "base64" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" + +[[package]] +name = "beefy-merkle-tree" +version = "4.0.0-dev" +source = "git+https://github.com/paritytech/substrate.git?branch=polkadot-v0.9.29#cc370aa61e15c18d23a2f686b812fd576a630afe" +dependencies = [ + "beefy-primitives", + "sp-api", + "tiny-keccak", +] + +[[package]] +name = "beefy-primitives" +version = "4.0.0-dev" +source = "git+https://github.com/paritytech/substrate.git?branch=polkadot-v0.9.29#cc370aa61e15c18d23a2f686b812fd576a630afe" +dependencies = [ + "parity-scale-codec", + "scale-info", + "sp-api", + "sp-application-crypto", + "sp-core", + "sp-runtime", + "sp-std", +] + +[[package]] +name = "bit-vec" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" + +[[package]] +name = "bitflags" +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 = "blake2" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b12e5fd123190ce1c2e559308a94c9bacad77907d4c6005d9e58fe1a0689e55e" +dependencies = [ + "digest 0.10.6", +] + +[[package]] +name = "blake2-rfc" +version = "0.2.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d6d530bdd2d52966a6d03b7a964add7ae1a288d25214066fd4b600f0f796400" +dependencies = [ + "arrayvec 0.4.12", + "constant_time_eq", +] + +[[package]] +name = "blake2b_simd" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "afa748e348ad3be8263be728124b24a24f268266f6f5d58af9d75f6a40b5c587" +dependencies = [ + "arrayref", + "arrayvec 0.5.2", + "constant_time_eq", +] + +[[package]] +name = "blake2s_simd" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e461a7034e85b211a4acb57ee2e6730b32912b06c08cc242243c39fc21ae6a2" +dependencies = [ + "arrayref", + "arrayvec 0.5.2", + "constant_time_eq", +] + +[[package]] +name = "block-buffer" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0940dc441f31689269e10ac70eb1002a3a1d3ad1390e030043662eb7fe4688b" +dependencies = [ + "block-padding 0.1.5", + "byte-tools", + "byteorder 1.4.3", + "generic-array 0.12.4", +] + +[[package]] +name = "block-buffer" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" +dependencies = [ + "block-padding 0.2.1", + "generic-array 0.14.6", +] + +[[package]] +name = "block-buffer" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cce20737498f97b993470a6e536b8523f0af7892a4f928cceb1ac5e52ebe7e" +dependencies = [ + "generic-array 0.14.6", +] + +[[package]] +name = "block-padding" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa79dedbb091f449f1f39e53edf88d5dbe95f895dae6135a8d7b881fb5af73f5" +dependencies = [ + "byte-tools", +] + +[[package]] +name = "block-padding" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d696c370c750c948ada61c69a0ee2cbbb9c50b1019ddb86d9317157a99c2cae" + +[[package]] +name = "bs58" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "771fe0050b883fcc3ea2359b1a96bcfbc090b7116eae7c3c512c7a083fdf23d3" + +[[package]] +name = "bumpalo" +version = "3.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "572f695136211188308f16ad2ca5c851a712c464060ae6974944458eb83880ba" + +[[package]] +name = "byte-slice-cast" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3ac9f8b63eca6fd385229b3675f6cc0dc5c8a5c8a54a59d4f52ffd670d87b0c" + +[[package]] +name = "byte-tools" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3b5ca7a04898ad4bcd41c90c5285445ff5b791899bb1b0abdd2a2aa791211d7" + +[[package]] +name = "byteorder" +version = "1.3.4" +source = "git+https://github.com/mesalock-linux/byteorder-sgx?tag=sgx_1.1.3#325f392dcd294109eb05f0a3c45e4141514c7784" +dependencies = [ + "sgx_tstd", +] + +[[package]] +name = "byteorder" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" + +[[package]] +name = "bytes" +version = "1.0.1" +source = "git+https://github.com/integritee-network/bytes-sgx?branch=sgx-experimental#62ed3082be2e23cb9bc8cc7ee9983a523de69292" +dependencies = [ + "sgx_tstd", +] + +[[package]] +name = "bytes" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfb24e866b15a1af2a1b663f10c6b6b8f397a84aadb828f12e5b289ec23a3a3c" + +[[package]] +name = "cc" +version = "1.0.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9f73505338f7d905b19d18738976aae232eb46b8efc15554ffc56deb5d9ebe4" + +[[package]] +name = "cfg-expr" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0aacacf4d96c24b2ad6eb8ee6df040e4f27b0d0b39a5710c30091baa830485db" +dependencies = [ + "smallvec 1.10.0", +] + +[[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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "chrono" +version = "0.4.11" +source = "git+https://github.com/mesalock-linux/chrono-sgx#f964ae7f5f65bd2c9cd6f44a067e7980afc08ca0" +dependencies = [ + "num-integer 0.1.41", + "num-traits 0.2.10", + "sgx_tstd", +] + +[[package]] +name = "chrono" +version = "0.4.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16b0a3d9ed01224b22057780a37bb8c5dbfe1be8ba48678e7bf57ec4b385411f" +dependencies = [ + "num-integer 0.1.45", + "num-traits 0.2.15", +] + +[[package]] +name = "cid" +version = "0.5.1" +source = "git+https://github.com/whalelephant/rust-cid?branch=nstd#cca87467c46106c801ca3727500477258b0f13b0" +dependencies = [ + "multibase", + "multihash", + "unsigned-varint", +] + +[[package]] +name = "cipher" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12f8e7987cbd042a63249497f41aed09f8e65add917ea6566effbc56578d6801" +dependencies = [ + "generic-array 0.14.6", +] + +[[package]] +name = "const-oid" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4c78c047431fee22c1a7bb92e00ad095a02a983affe4d8a72e2a2c62c1b94f3" + +[[package]] +name = "constant_time_eq" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" + +[[package]] +name = "convert_case" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" + +[[package]] +name = "cpufeatures" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28d997bd5e24a5928dd43e46dc529867e207907fe0b239c3477d924f7f2ca320" +dependencies = [ + "libc", +] + +[[package]] +name = "crunchy" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" + +[[package]] +name = "crypto-bigint" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03c6a1d5fa1de37e071642dfa44ec552ca5b299adb128fab16138e24b548fd21" +dependencies = [ + "generic-array 0.14.6", + "rand_core 0.6.4", + "subtle", + "zeroize", +] + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array 0.14.6", + "typenum 1.15.0", +] + +[[package]] +name = "crypto-mac" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1d1a86f49236c215f271d40892d5fc950490551400b02ef360692c29815c714" +dependencies = [ + "generic-array 0.14.6", + "subtle", +] + +[[package]] +name = "curve25519-dalek" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a9b85542f99a2dfa2a1b8e192662741c9859a846b296bef1c92ef9b58b5a216" +dependencies = [ + "byteorder 1.4.3", + "digest 0.8.1", + "rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", + "subtle", + "zeroize", +] + +[[package]] +name = "curve25519-dalek" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b9fdf9972b2bd6af2d913799d9ebc165ea4d2e65878e329d9c6b372c4491b61" +dependencies = [ + "byteorder 1.4.3", + "digest 0.9.0", + "rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", + "subtle", + "zeroize", +] + +[[package]] +name = "data-encoding" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ee2393c4a91429dffb4bedf19f4d6abf27d8a732c8ce4980305d782e5426d57" + +[[package]] +name = "der" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6919815d73839e7ad218de758883aae3a257ba6759ce7a9992501efbb53d705c" +dependencies = [ + "const-oid", +] + +[[package]] +name = "derivative" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" +dependencies = [ + "proc-macro2", + "quote 1.0.21", + "syn 1.0.103", +] + +[[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 1.0.21", + "rustc_version 0.4.0", + "syn 1.0.103", +] + +[[package]] +name = "digest" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3d0c8c8752312f9713efd397ff63acb9f85585afbf179282e720e7704954dd5" +dependencies = [ + "generic-array 0.12.4", +] + +[[package]] +name = "digest" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" +dependencies = [ + "generic-array 0.14.6", +] + +[[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", + "crypto-common", + "subtle", +] + +[[package]] +name = "ecdsa" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0d69ae62e0ce582d56380743515fefaf1a8c70cec685d9677636d7e30ae9dc9" +dependencies = [ + "der", + "elliptic-curve", + "rfc6979", + "signature", +] + +[[package]] +name = "ed25519-zebra" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c24f403d068ad0b359e577a77f92392118be3f3c927538f2bb544a5ecd828c6" +dependencies = [ + "curve25519-dalek 3.2.0", + "hashbrown 0.12.3", + "hex 0.4.3", + "rand_core 0.6.4", + "sha2 0.9.9", + "zeroize", +] + +[[package]] +name = "either" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90e5c1c8368803113bf0c9584fc495a58b86dc8a29edbf8fe877d21d9507e797" + +[[package]] +name = "elliptic-curve" +version = "0.11.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25b477563c2bfed38a3b7a60964c49e058b2510ad3f12ba3483fd8f62c2306d6" +dependencies = [ + "base16ct", + "crypto-bigint", + "der", + "ff", + "generic-array 0.14.6", + "group", + "rand_core 0.6.4", + "sec1", + "subtle", + "zeroize", +] + +[[package]] +name = "enclave-runtime" +version = "0.9.0" +dependencies = [ + "cid", + "derive_more", + "env_logger", + "frame-support", + "frame-system", + "hex 0.4.3", + "ipfs-unixfs", + "ita-exchange-oracle", + "ita-sgx-runtime", + "ita-stf", + "itc-direct-rpc-server", + "itc-offchain-worker-executor", + "itc-parentchain", + "itc-parentchain-test", + "itc-tls-websocket-server", + "itp-attestation-handler", + "itp-block-import-queue", + "itp-component-container", + "itp-extrinsics-factory", + "itp-hashing", + "itp-node-api", + "itp-nonce-cache", + "itp-ocall-api", + "itp-primitives-cache", + "itp-rpc", + "itp-settings", + "itp-sgx-crypto", + "itp-sgx-externalities", + "itp-sgx-io", + "itp-stf-executor", + "itp-stf-interface", + "itp-stf-state-handler", + "itp-stf-state-observer", + "itp-storage", + "itp-teerex-storage", + "itp-test", + "itp-time-utils", + "itp-top-pool", + "itp-top-pool-author", + "itp-types", + "itp-utils", + "its-block-verification", + "its-primitives", + "its-sidechain", + "jsonrpc-core", + "lazy_static", + "lc-stf-task-receiver", + "lc-stf-task-sender", + "litentry-primitives", + "log", + "multibase", + "parity-scale-codec", + "primitive-types", + "rust-base58", + "rustls 0.19.0 (git+https://github.com/mesalock-linux/rustls?rev=sgx_1.1.3)", + "serde 1.0.118 (git+https://github.com/mesalock-linux/serde-sgx?tag=sgx_1.1.3)", + "serde_derive 1.0.118", + "serde_json 1.0.60 (git+https://github.com/mesalock-linux/serde-json-sgx?tag=sgx_1.1.3)", + "sgx_rand", + "sgx_serialize", + "sgx_serialize_derive", + "sgx_tcrypto", + "sgx_tcrypto_helper", + "sgx_trts", + "sgx_tse", + "sgx_tseal", + "sgx_tstd", + "sgx_tunittest", + "sgx_types", + "sp-core", + "sp-runtime", + "sp-std", + "substrate-api-client", + "webpki", +] + +[[package]] +name = "env_logger" +version = "0.9.0" +source = "git+https://github.com/integritee-network/env_logger-sgx#55745829b2ae8a77f0915af3671ec8a9a00cace9" +dependencies = [ + "humantime", + "log", + "regex 1.3.1", + "sgx_tstd", + "termcolor", +] + +[[package]] +name = "environmental" +version = "1.1.3" +dependencies = [ + "sgx_tstd", +] + +[[package]] +name = "environmental" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68b91989ae21441195d7d9b9993a2f9295c7e1a8c96255d8b729accddc124797" + +[[package]] +name = "ethbloom" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11da94e443c60508eb62cf256243a64da87304c2802ac2528847f79d750007ef" +dependencies = [ + "crunchy", + "fixed-hash", + "impl-codec", + "impl-rlp", + "scale-info", + "tiny-keccak", +] + +[[package]] +name = "ethereum" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23750149fe8834c0e24bb9adcbacbe06c45b9861f15df53e09f26cb7c4ab91ef" +dependencies = [ + "bytes 1.3.0", + "ethereum-types", + "hash-db", + "hash256-std-hasher", + "parity-scale-codec", + "rlp", + "rlp-derive", + "scale-info", + "sha3 0.10.6", + "triehash", +] + +[[package]] +name = "ethereum-types" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2827b94c556145446fcce834ca86b7abf0c39a805883fe20e72c5bfdb5a0dc6" +dependencies = [ + "ethbloom", + "fixed-hash", + "impl-codec", + "impl-rlp", + "primitive-types", + "scale-info", + "uint", +] + +[[package]] +name = "evm" +version = "0.35.0" +source = "git+https://github.com/rust-blockchain/evm?rev=51b8c2ce3104265e1fd5bb0fe5cdfd2e0938239c#51b8c2ce3104265e1fd5bb0fe5cdfd2e0938239c" +dependencies = [ + "auto_impl", + "ethereum", + "evm-core", + "evm-gasometer", + "evm-runtime", + "log", + "parity-scale-codec", + "primitive-types", + "rlp", + "scale-info", + "sha3 0.10.6", +] + +[[package]] +name = "evm-core" +version = "0.35.0" +source = "git+https://github.com/rust-blockchain/evm?rev=51b8c2ce3104265e1fd5bb0fe5cdfd2e0938239c#51b8c2ce3104265e1fd5bb0fe5cdfd2e0938239c" +dependencies = [ + "parity-scale-codec", + "primitive-types", + "scale-info", +] + +[[package]] +name = "evm-gasometer" +version = "0.35.0" +source = "git+https://github.com/rust-blockchain/evm?rev=51b8c2ce3104265e1fd5bb0fe5cdfd2e0938239c#51b8c2ce3104265e1fd5bb0fe5cdfd2e0938239c" +dependencies = [ + "evm-core", + "evm-runtime", + "primitive-types", +] + +[[package]] +name = "evm-runtime" +version = "0.35.0" +source = "git+https://github.com/rust-blockchain/evm?rev=51b8c2ce3104265e1fd5bb0fe5cdfd2e0938239c#51b8c2ce3104265e1fd5bb0fe5cdfd2e0938239c" +dependencies = [ + "auto_impl", + "evm-core", + "primitive-types", + "sha3 0.10.6", +] + +[[package]] +name = "fake-simd" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed" + +[[package]] +name = "ff" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "131655483be284720a17d74ff97592b8e76576dc25563148601df2d7c9080924" +dependencies = [ + "rand_core 0.6.4", + "subtle", +] + +[[package]] +name = "finality-grandpa" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b22349c6a11563a202d95772a68e0fcf56119e74ea8a2a19cf2301460fcd0df5" +dependencies = [ + "either", + "futures 0.3.25", + "num-traits 0.2.15", + "parity-scale-codec", + "scale-info", +] + +[[package]] +name = "fixed-hash" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcf0ed7fe52a17a03854ec54a9f76d6d84508d1c0e66bc1793301c73fc8493c" +dependencies = [ + "byteorder 1.4.3", + "rustc-hex", + "static_assertions", +] + +[[package]] +name = "fnv" +version = "1.0.6" +source = "git+https://github.com/mesalock-linux/rust-fnv-sgx#c3bd6153c1403c1fa32fa54be5544d91f5efb017" +dependencies = [ + "hashbrown 0.3.1", +] + +[[package]] +name = "fp-evm" +version = "3.0.0-dev" +source = "git+https://github.com/integritee-network/frontier.git?branch=polkadot-v0.9.29#ee051c19f7dc7d54f0c092275db20d0a56a751af" +dependencies = [ + "evm", + "frame-support", + "parity-scale-codec", + "sp-core", + "sp-std", +] + +[[package]] +name = "frame-executive" +version = "4.0.0-dev" +source = "git+https://github.com/paritytech/substrate.git?branch=polkadot-v0.9.29#cc370aa61e15c18d23a2f686b812fd576a630afe" +dependencies = [ + "frame-support", + "frame-system", + "parity-scale-codec", + "scale-info", + "sp-core", + "sp-io", + "sp-runtime", + "sp-std", + "sp-tracing", +] + +[[package]] +name = "frame-metadata" +version = "15.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df6bb8542ef006ef0de09a5c4420787d79823c0ed7924225822362fd2bf2ff2d" +dependencies = [ + "cfg-if 1.0.0", + "parity-scale-codec", + "scale-info", +] + +[[package]] +name = "frame-metadata" +version = "15.0.0" +source = "git+https://github.com/integritee-network/frame-metadata#3b43da9821238681f9431276d55b92a079142083" +dependencies = [ + "cfg-if 1.0.0", + "parity-scale-codec", + "scale-info", + "serde 1.0.147", +] + +[[package]] +name = "frame-support" +version = "4.0.0-dev" +source = "git+https://github.com/paritytech/substrate.git?branch=polkadot-v0.9.29#cc370aa61e15c18d23a2f686b812fd576a630afe" +dependencies = [ + "bitflags", + "frame-metadata 15.0.0 (registry+https://github.com/rust-lang/crates.io-index)", + "frame-support-procedural", + "impl-trait-for-tuples", + "k256", + "log", + "parity-scale-codec", + "paste", + "scale-info", + "smallvec 1.10.0", + "sp-api", + "sp-arithmetic", + "sp-core", + "sp-core-hashing-proc-macro", + "sp-inherents", + "sp-io", + "sp-runtime", + "sp-staking", + "sp-std", + "sp-tracing", + "tt-call", +] + +[[package]] +name = "frame-support-procedural" +version = "4.0.0-dev" +source = "git+https://github.com/paritytech/substrate.git?branch=polkadot-v0.9.29#cc370aa61e15c18d23a2f686b812fd576a630afe" +dependencies = [ + "Inflector", + "cfg-expr", + "frame-support-procedural-tools", + "itertools", + "proc-macro2", + "quote 1.0.21", + "syn 1.0.103", +] + +[[package]] +name = "frame-support-procedural-tools" +version = "4.0.0-dev" +source = "git+https://github.com/paritytech/substrate.git?branch=polkadot-v0.9.29#cc370aa61e15c18d23a2f686b812fd576a630afe" +dependencies = [ + "frame-support-procedural-tools-derive", + "proc-macro-crate", + "proc-macro2", + "quote 1.0.21", + "syn 1.0.103", +] + +[[package]] +name = "frame-support-procedural-tools-derive" +version = "3.0.0" +source = "git+https://github.com/paritytech/substrate.git?branch=polkadot-v0.9.29#cc370aa61e15c18d23a2f686b812fd576a630afe" +dependencies = [ + "proc-macro2", + "quote 1.0.21", + "syn 1.0.103", +] + +[[package]] +name = "frame-system" +version = "4.0.0-dev" +source = "git+https://github.com/paritytech/substrate.git?branch=polkadot-v0.9.29#cc370aa61e15c18d23a2f686b812fd576a630afe" +dependencies = [ + "frame-support", + "log", + "parity-scale-codec", + "scale-info", + "sp-core", + "sp-io", + "sp-runtime", + "sp-std", + "sp-version", +] + +[[package]] +name = "frame-system-rpc-runtime-api" +version = "4.0.0-dev" +source = "git+https://github.com/paritytech/substrate.git?branch=polkadot-v0.9.29#cc370aa61e15c18d23a2f686b812fd576a630afe" +dependencies = [ + "parity-scale-codec", + "sp-api", +] + +[[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.8" +source = "git+https://github.com/mesalock-linux/futures-rs-sgx#d54882f24ddf7d61327a067b2f608d6940a36444" +dependencies = [ + "futures-channel 0.3.8", + "futures-core 0.3.8", + "futures-executor", + "futures-io 0.3.8", + "futures-sink 0.3.8", + "futures-task 0.3.8", + "futures-util 0.3.8", + "sgx_tstd", +] + +[[package]] +name = "futures" +version = "0.3.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38390104763dc37a5145a53c29c63c1290b5d316d6086ec32c293f6736051bb0" +dependencies = [ + "futures-channel 0.3.25", + "futures-core 0.3.25", + "futures-io 0.3.25", + "futures-sink 0.3.25", + "futures-task 0.3.25", + "futures-util 0.3.25", +] + +[[package]] +name = "futures-channel" +version = "0.3.8" +source = "git+https://github.com/mesalock-linux/futures-rs-sgx#d54882f24ddf7d61327a067b2f608d6940a36444" +dependencies = [ + "futures-core 0.3.8", + "futures-sink 0.3.8", + "sgx_tstd", +] + +[[package]] +name = "futures-channel" +version = "0.3.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52ba265a92256105f45b719605a571ffe2d1f0fea3807304b522c1d778f79eed" +dependencies = [ + "futures-core 0.3.25", + "futures-sink 0.3.25", +] + +[[package]] +name = "futures-core" +version = "0.3.8" +source = "git+https://github.com/mesalock-linux/futures-rs-sgx#d54882f24ddf7d61327a067b2f608d6940a36444" +dependencies = [ + "sgx_tstd", +] + +[[package]] +name = "futures-core" +version = "0.3.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04909a7a7e4633ae6c4a9ab280aeb86da1236243a77b694a49eacd659a4bd3ac" + +[[package]] +name = "futures-executor" +version = "0.3.8" +source = "git+https://github.com/mesalock-linux/futures-rs-sgx#d54882f24ddf7d61327a067b2f608d6940a36444" +dependencies = [ + "futures-core 0.3.8", + "futures-task 0.3.8", + "futures-util 0.3.8", + "sgx_tstd", +] + +[[package]] +name = "futures-io" +version = "0.3.8" +source = "git+https://github.com/mesalock-linux/futures-rs-sgx#d54882f24ddf7d61327a067b2f608d6940a36444" +dependencies = [ + "sgx_tstd", +] + +[[package]] +name = "futures-io" +version = "0.3.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00f5fb52a06bdcadeb54e8d3671f8888a39697dcb0b81b23b55174030427f4eb" + +[[package]] +name = "futures-macro" +version = "0.3.8" +source = "git+https://github.com/mesalock-linux/futures-rs-sgx#d54882f24ddf7d61327a067b2f608d6940a36444" +dependencies = [ + "proc-macro-hack", + "proc-macro2", + "quote 1.0.21", + "syn 1.0.103", +] + +[[package]] +name = "futures-sink" +version = "0.3.8" +source = "git+https://github.com/mesalock-linux/futures-rs-sgx#d54882f24ddf7d61327a067b2f608d6940a36444" + +[[package]] +name = "futures-sink" +version = "0.3.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39c15cf1a4aa79df40f1bb462fb39676d0ad9e366c2a33b590d7c66f4f81fcf9" + +[[package]] +name = "futures-task" +version = "0.3.8" +source = "git+https://github.com/mesalock-linux/futures-rs-sgx#d54882f24ddf7d61327a067b2f608d6940a36444" +dependencies = [ + "once_cell 1.4.0", + "sgx_tstd", +] + +[[package]] +name = "futures-task" +version = "0.3.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ffb393ac5d9a6eaa9d3fdf37ae2776656b706e200c8e16b1bdb227f5198e6ea" + +[[package]] +name = "futures-util" +version = "0.3.8" +source = "git+https://github.com/mesalock-linux/futures-rs-sgx#d54882f24ddf7d61327a067b2f608d6940a36444" +dependencies = [ + "futures-channel 0.3.8", + "futures-core 0.3.8", + "futures-io 0.3.8", + "futures-macro", + "futures-sink 0.3.8", + "futures-task 0.3.8", + "memchr 2.2.1", + "pin-project-lite", + "pin-utils", + "proc-macro-hack", + "proc-macro-nested", + "sgx_tstd", + "slab 0.4.2", +] + +[[package]] +name = "futures-util" +version = "0.3.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "197676987abd2f9cadff84926f410af1c183608d36641465df73ae8211dc65d6" +dependencies = [ + "futures-core 0.3.25", + "futures-sink 0.3.25", + "futures-task 0.3.25", + "pin-project-lite", + "pin-utils", +] + +[[package]] +name = "generic-array" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffdf9f34f1447443d37393cc6c2b8313aebddcd96906caf34e54c68d8e57d7bd" +dependencies = [ + "typenum 1.15.0", +] + +[[package]] +name = "generic-array" +version = "0.14.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bff49e947297f3312447abdca79f45f4738097cc82b06e72054d2223f601f1b9" +dependencies = [ + "typenum 1.15.0", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.1.14" +source = "git+https://github.com/mesalock-linux/getrandom-sgx#0aa9cc20c7dea713ccaac2c44430d625a395ebae" +dependencies = [ + "cfg-if 0.1.10", + "sgx_libc", + "sgx_trts", + "sgx_tstd", +] + +[[package]] +name = "getrandom" +version = "0.2.3" +source = "git+https://github.com/integritee-network/getrandom-sgx?branch=update-v2.3#0a4af01fe1df0e6200192e7a709fd18da413466e" +dependencies = [ + "cfg-if 1.0.0", + "sgx_libc", + "sgx_trts", + "sgx_tstd", +] + +[[package]] +name = "group" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc5ac374b108929de78460075f3dc439fa66df9d8fc77e8f12caa5165fcf0c89" +dependencies = [ + "ff", + "rand_core 0.6.4", + "subtle", +] + +[[package]] +name = "hash-db" +version = "0.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d23bd4e7b5eda0d0f3a307e8b381fdc8ba9000f26fbe912250c0a4cc3956364a" + +[[package]] +name = "hash256-std-hasher" +version = "0.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92c171d55b98633f4ed3860808f004099b36c1cc29c42cfc53aa8591b21efcf2" +dependencies = [ + "crunchy", +] + +[[package]] +name = "hashbrown" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29fba9abe4742d586dfd0c06ae4f7e73a1c2d86b856933509b269d82cdf06e18" + +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" +dependencies = [ + "ahash", +] + +[[package]] +name = "hashbrown_tstd" +version = "0.12.0" +source = "git+https://github.com/apache/incubator-teaclave-sgx-sdk?branch=v1.1.6-testing#9c1bbd52f188f600a212b57c916124245da1b7fd" + +[[package]] +name = "hex" +version = "0.4.0" +source = "git+https://github.com/mesalock-linux/rust-hex-sgx?tag=sgx_1.1.3#ee3266cd29b9f9c2eb69af9487f55c4f09c38f2b" +dependencies = [ + "sgx_tstd", +] + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "hmac" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a2a2320eb7ec0ebe8da8f744d7812d9fc4cb4d09344ac01898dbcb6a20ae69b" +dependencies = [ + "crypto-mac", + "digest 0.9.0", +] + +[[package]] +name = "http" +version = "0.2.1" +source = "git+https://github.com/integritee-network/http-sgx.git?branch=sgx-experimental#307b5421fb7a489a114bede0dc05c8d32b804f49" +dependencies = [ + "bytes 1.0.1", + "fnv", + "itoa 0.4.5", + "sgx_tstd", +] + +[[package]] +name = "http_req" +version = "0.8.1" +source = "git+https://github.com/integritee-network/http_req#3723e88235f2b29bc1a31835853b072ffd0455fd" +dependencies = [ + "log", + "rustls 0.19.0 (git+https://github.com/mesalock-linux/rustls?branch=mesalock_sgx)", + "sgx_tstd", + "unicase", + "webpki", + "webpki-roots 0.21.0 (git+https://github.com/mesalock-linux/webpki-roots?branch=mesalock_sgx)", +] + +[[package]] +name = "httparse" +version = "1.4.1" +source = "git+https://github.com/integritee-network/httparse-sgx?branch=sgx-experimental#cc97e4b34d2c44a1e3df5bdebef446b9771f5cc3" +dependencies = [ + "sgx_tstd", +] + +[[package]] +name = "humantime" +version = "1.3.0" +source = "git+https://github.com/mesalock-linux/humantime-sgx#c5243dfa36002c01adbc9aade288ead1b2c411cc" +dependencies = [ + "quick-error", + "sgx_tstd", +] + +[[package]] +name = "idna" +version = "0.2.0" +source = "git+https://github.com/mesalock-linux/rust-url-sgx?tag=sgx_1.1.3#23832f3191456c2d4a0faab10952e1747be58ca8" +dependencies = [ + "matches", + "sgx_tstd", + "unicode-bidi", + "unicode-normalization", +] + +[[package]] +name = "impl-codec" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba6a270039626615617f3f36d15fc827041df3b78c439da2cadfa47455a77f2f" +dependencies = [ + "parity-scale-codec", +] + +[[package]] +name = "impl-rlp" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f28220f89297a075ddc7245cd538076ee98b01f2a9c23a53a4f1105d5a322808" +dependencies = [ + "rlp", +] + +[[package]] +name = "impl-serde" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4551f042f3438e64dbd6226b20527fc84a6e1fe65688b58746a2f53623f25f5c" +dependencies = [ + "serde 1.0.147", +] + +[[package]] +name = "impl-trait-for-tuples" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11d7a9f6330b71fea57921c9b61c47ee6e84f72d394754eff6163ae67e7395eb" +dependencies = [ + "proc-macro2", + "quote 1.0.21", + "syn 1.0.103", +] + +[[package]] +name = "integer-sqrt" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "276ec31bcb4a9ee45f58bec6f9ec700ae4cf4f4f8f2fa7e06cb406bd5ffdd770" +dependencies = [ + "num-traits 0.2.15", +] + +[[package]] +name = "iovec" +version = "0.1.4" +source = "git+https://github.com/mesalock-linux/iovec-sgx#5c2f8e81925b4c06c556d856f3237461b00e27c9" +dependencies = [ + "sgx_libc", +] + +[[package]] +name = "ipfs-unixfs" +version = "0.0.1" +source = "git+https://github.com/whalelephant/rust-ipfs?branch=w-nstd#52f84dceea7065bb4ee2c24da53b3bedf162241a" +dependencies = [ + "cid", + "either", + "multihash", + "quick-protobuf", + "sha2 0.9.9", +] + +[[package]] +name = "ita-exchange-oracle" +version = "0.9.0" +dependencies = [ + "itc-rest-client", + "itp-enclave-metrics", + "itp-ocall-api", + "lazy_static", + "log", + "parity-scale-codec", + "serde 1.0.147", + "serde_json 1.0.89", + "sgx_tstd", + "substrate-fixed", + "thiserror 1.0.9", + "url", +] + +[[package]] +name = "ita-sgx-runtime" +version = "0.9.0" +dependencies = [ + "frame-executive", + "frame-support", + "frame-system", + "frame-system-rpc-runtime-api", + "itp-sgx-runtime-primitives", + "litentry-primitives", + "pallet-aura", + "pallet-balances", + "pallet-evm", + "pallet-grandpa", + "pallet-identity-management 0.1.0", + "pallet-parentchain", + "pallet-randomness-collective-flip", + "pallet-sudo", + "pallet-timestamp", + "pallet-transaction-payment", + "pallet-transaction-payment-rpc-runtime-api", + "parity-scale-codec", + "scale-info", + "sp-api", + "sp-block-builder", + "sp-consensus-aura", + "sp-core", + "sp-inherents", + "sp-offchain", + "sp-runtime", + "sp-session", + "sp-std", + "sp-transaction-pool", + "sp-version", +] + +[[package]] +name = "ita-stf" +version = "0.9.0" +dependencies = [ + "derive_more", + "frame-support", + "frame-system", + "ita-sgx-runtime", + "itp-hashing", + "itp-node-api", + "itp-node-api-metadata", + "itp-node-api-metadata-provider", + "itp-sgx-externalities", + "itp-stf-interface", + "itp-storage", + "itp-types", + "itp-utils", + "lc-stf-task-sender", + "litentry-primitives", + "log", + "pallet-balances", + "pallet-parentchain", + "pallet-sudo", + "parity-scale-codec", + "rand 0.7.3", + "ring 0.16.20", + "rlp", + "sgx_tstd", + "sha3 0.10.6", + "sp-application-crypto", + "sp-core", + "sp-io", + "sp-runtime", +] + +[[package]] +name = "itc-direct-rpc-server" +version = "0.9.0" +dependencies = [ + "itc-tls-websocket-server", + "itp-rpc", + "itp-types", + "itp-utils", + "jsonrpc-core", + "log", + "parity-scale-codec", + "serde_json 1.0.89", + "sgx_tstd", + "sgx_types", + "sp-runtime", + "thiserror 1.0.9", +] + +[[package]] +name = "itc-offchain-worker-executor" +version = "0.9.0" +dependencies = [ + "ita-stf", + "itc-parentchain-light-client", + "itp-extrinsics-factory", + "itp-stf-executor", + "itp-stf-interface", + "itp-stf-state-handler", + "itp-top-pool-author", + "itp-types", + "log", + "sgx_tstd", + "sgx_types", + "sp-core", + "sp-runtime", + "thiserror 1.0.9", +] + +[[package]] +name = "itc-parentchain" +version = "0.9.0" +dependencies = [ + "itc-parentchain-block-import-dispatcher", + "itc-parentchain-block-importer", + "itc-parentchain-indirect-calls-executor", + "itc-parentchain-light-client", + "itp-types", + "parity-scale-codec", + "sp-runtime", +] + +[[package]] +name = "itc-parentchain-block-import-dispatcher" +version = "0.9.0" +dependencies = [ + "itc-parentchain-block-importer", + "itp-block-import-queue", + "log", + "sgx_tstd", + "sgx_types", + "sp-runtime", + "thiserror 1.0.9", +] + +[[package]] +name = "itc-parentchain-block-importer" +version = "0.9.0" +dependencies = [ + "ita-stf", + "itc-parentchain-indirect-calls-executor", + "itc-parentchain-light-client", + "itp-extrinsics-factory", + "itp-settings", + "itp-stf-executor", + "itp-types", + "log", + "parity-scale-codec", + "sgx_tstd", + "sgx_types", + "sp-runtime", + "thiserror 1.0.9", +] + +[[package]] +name = "itc-parentchain-indirect-calls-executor" +version = "0.9.0" +dependencies = [ + "beefy-merkle-tree", + "bs58", + "futures 0.3.8", + "ita-sgx-runtime", + "ita-stf", + "itp-node-api", + "itp-ocall-api", + "itp-sgx-crypto", + "itp-stf-executor", + "itp-top-pool-author", + "itp-types", + "litentry-primitives", + "log", + "pallet-identity-management 0.1.0 (git+https://github.com/litentry/litentry-parachain.git?branch=tee-dev)", + "parity-scale-codec", + "sgx_tstd", + "sgx_types", + "sp-core", + "sp-runtime", + "substrate-api-client", + "thiserror 1.0.9", +] + +[[package]] +name = "itc-parentchain-light-client" +version = "0.9.0" +dependencies = [ + "derive_more", + "finality-grandpa", + "frame-system", + "hash-db", + "itc-parentchain-test", + "itp-ocall-api", + "itp-settings", + "itp-sgx-io", + "itp-storage", + "itp-types", + "lazy_static", + "log", + "num-traits 0.2.15", + "parity-scale-codec", + "sgx_tstd", + "sgx_types", + "sp-application-crypto", + "sp-core", + "sp-finality-grandpa", + "sp-runtime", + "sp-trie", + "thiserror 1.0.9", +] + +[[package]] +name = "itc-parentchain-test" +version = "0.9.0" +dependencies = [ + "frame-support", + "frame-system", + "itp-types", + "log", + "parity-scale-codec", + "scale-info", + "sp-core", + "sp-io", + "sp-runtime", + "sp-std", +] + +[[package]] +name = "itc-rest-client" +version = "0.9.0" +dependencies = [ + "base64 0.13.0 (git+https://github.com/mesalock-linux/rust-base64-sgx?rev=sgx_1.1.3)", + "http", + "http_req", + "log", + "serde 1.0.147", + "serde_json 1.0.89", + "sgx_tstd", + "sgx_types", + "thiserror 1.0.9", + "url", +] + +[[package]] +name = "itc-tls-websocket-server" +version = "0.9.0" +dependencies = [ + "bit-vec", + "chrono 0.4.23", + "log", + "mio", + "mio-extras", + "rcgen", + "rustls 0.19.0 (git+https://github.com/mesalock-linux/rustls?branch=mesalock_sgx)", + "sgx_crypto_helper", + "sgx_tstd", + "sgx_types", + "sp-core", + "thiserror 1.0.9", + "tungstenite", + "webpki", + "yasna", +] + +[[package]] +name = "itertools" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "0.4.5" +source = "git+https://github.com/mesalock-linux/itoa-sgx#295ee451f5ec74f25c299552b481beb445ea3eb7" +dependencies = [ + "sgx_tstd", +] + +[[package]] +name = "itoa" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4217ad341ebadf8d8e724e264f13e593e0648f5b3e94b3896a5df283be015ecc" + +[[package]] +name = "itp-api-client-types" +version = "0.9.0" +dependencies = [ + "substrate-api-client", +] + +[[package]] +name = "itp-attestation-handler" +version = "0.8.0" +dependencies = [ + "arrayvec 0.7.2", + "base64 0.13.0 (git+https://github.com/mesalock-linux/rust-base64-sgx?rev=sgx_1.1.3)", + "bit-vec", + "chrono 0.4.11", + "hex 0.4.3", + "httparse", + "itertools", + "itp-ocall-api", + "itp-settings", + "itp-sgx-crypto", + "itp-sgx-io", + "itp-types", + "log", + "num-bigint", + "parity-scale-codec", + "rustls 0.19.0 (git+https://github.com/mesalock-linux/rustls?rev=sgx_1.1.3)", + "serde_json 1.0.60 (git+https://github.com/mesalock-linux/serde-json-sgx?tag=sgx_1.1.3)", + "sgx_rand", + "sgx_tcrypto", + "sgx_tse", + "sgx_tstd", + "sgx_types", + "sp-core", + "sp-runtime", + "thiserror 1.0.9", + "webpki", + "webpki-roots 0.21.0 (git+https://github.com/mesalock-linux/webpki-roots?branch=mesalock_sgx)", + "yasna", +] + +[[package]] +name = "itp-block-import-queue" +version = "0.8.0" +dependencies = [ + "sgx_tstd", + "sgx_types", + "thiserror 1.0.9", +] + +[[package]] +name = "itp-component-container" +version = "0.8.0" +dependencies = [ + "sgx_tstd", + "thiserror 1.0.9", +] + +[[package]] +name = "itp-enclave-metrics" +version = "0.9.0" +dependencies = [ + "parity-scale-codec", + "sgx_tstd", + "substrate-fixed", +] + +[[package]] +name = "itp-extrinsics-factory" +version = "0.9.0" +dependencies = [ + "itp-node-api", + "itp-nonce-cache", + "itp-types", + "log", + "parity-scale-codec", + "sgx_tstd", + "sgx_types", + "sp-core", + "sp-runtime", + "substrate-api-client", + "thiserror 1.0.9", +] + +[[package]] +name = "itp-hashing" +version = "0.9.0" +dependencies = [ + "sp-core", +] + +[[package]] +name = "itp-node-api" +version = "0.9.0" +dependencies = [ + "itp-api-client-types", + "itp-node-api-metadata", + "itp-node-api-metadata-provider", +] + +[[package]] +name = "itp-node-api-metadata" +version = "0.9.0" +dependencies = [ + "parity-scale-codec", + "sp-core", + "substrate-api-client", +] + +[[package]] +name = "itp-node-api-metadata-provider" +version = "0.9.0" +dependencies = [ + "itp-node-api-metadata", + "sgx_tstd", + "thiserror 1.0.9", +] + +[[package]] +name = "itp-nonce-cache" +version = "0.8.0" +dependencies = [ + "lazy_static", + "sgx_tstd", + "thiserror 1.0.9", +] + +[[package]] +name = "itp-ocall-api" +version = "0.9.0" +dependencies = [ + "derive_more", + "itp-storage", + "itp-types", + "parity-scale-codec", + "sgx_types", + "sp-core", + "sp-runtime", + "sp-std", +] + +[[package]] +name = "itp-primitives-cache" +version = "0.9.0" +dependencies = [ + "lazy_static", + "sgx_tstd", + "thiserror 1.0.9", +] + +[[package]] +name = "itp-rpc" +version = "0.9.0" +dependencies = [ + "itp-types", + "parity-scale-codec", + "serde 1.0.147", + "serde_json 1.0.89", + "sgx_tstd", +] + +[[package]] +name = "itp-settings" +version = "0.9.0" + +[[package]] +name = "itp-sgx-crypto" +version = "0.9.0" +dependencies = [ + "aes", + "derive_more", + "itp-settings", + "itp-sgx-io", + "log", + "ofb", + "parity-scale-codec", + "serde 1.0.118 (git+https://github.com/mesalock-linux/serde-sgx?tag=sgx_1.1.3)", + "serde_json 1.0.60 (git+https://github.com/mesalock-linux/serde-json-sgx?tag=sgx_1.1.3)", + "sgx_crypto_helper", + "sgx_rand", + "sgx_tstd", + "sgx_types", + "sp-core", +] + +[[package]] +name = "itp-sgx-externalities" +version = "0.9.0" +dependencies = [ + "derive_more", + "environmental 1.1.3", + "itp-hashing", + "log", + "parity-scale-codec", + "postcard", + "serde 1.0.147", + "sgx_tstd", + "sp-core", +] + +[[package]] +name = "itp-sgx-io" +version = "0.8.0" +dependencies = [ + "sgx_tstd", +] + +[[package]] +name = "itp-sgx-runtime-primitives" +version = "0.9.0" +dependencies = [ + "frame-system", + "pallet-balances", + "sp-core", + "sp-runtime", +] + +[[package]] +name = "itp-stf-executor" +version = "0.9.0" +dependencies = [ + "ita-sgx-runtime", + "ita-stf", + "itc-parentchain-test", + "itp-node-api", + "itp-ocall-api", + "itp-sgx-crypto", + "itp-sgx-externalities", + "itp-stf-interface", + "itp-stf-state-handler", + "itp-stf-state-observer", + "itp-storage", + "itp-test", + "itp-time-utils", + "itp-top-pool-author", + "itp-types", + "log", + "parity-scale-codec", + "sgx_crypto_helper", + "sgx_tstd", + "sgx_types", + "sp-core", + "sp-runtime", + "substrate-api-client", + "thiserror 1.0.9", +] + +[[package]] +name = "itp-stf-interface" +version = "0.8.0" +dependencies = [ + "itp-node-api-metadata", + "itp-node-api-metadata-provider", + "itp-types", +] + +[[package]] +name = "itp-stf-state-handler" +version = "0.9.0" +dependencies = [ + "ita-stf", + "itp-hashing", + "itp-settings", + "itp-sgx-crypto", + "itp-sgx-externalities", + "itp-sgx-io", + "itp-stf-interface", + "itp-stf-state-observer", + "itp-time-utils", + "itp-types", + "lazy_static", + "log", + "parity-scale-codec", + "rust-base58", + "sgx_tcrypto", + "sgx_tstd", + "sgx_types", + "sp-core", + "thiserror 1.0.9", +] + +[[package]] +name = "itp-stf-state-observer" +version = "0.9.0" +dependencies = [ + "itp-types", + "log", + "parity-scale-codec", + "sgx_tstd", + "thiserror 1.0.9", +] + +[[package]] +name = "itp-storage" +version = "0.9.0" +dependencies = [ + "derive_more", + "frame-metadata 15.0.0 (registry+https://github.com/rust-lang/crates.io-index)", + "frame-support", + "hash-db", + "itp-types", + "parity-scale-codec", + "sgx_tstd", + "sp-core", + "sp-runtime", + "sp-std", + "sp-trie", + "thiserror 1.0.9", +] + +[[package]] +name = "itp-teerex-storage" +version = "0.9.0" +dependencies = [ + "itp-storage", + "sp-std", +] + +[[package]] +name = "itp-test" +version = "0.9.0" +dependencies = [ + "derive_more", + "ita-stf", + "itp-ocall-api", + "itp-sgx-crypto", + "itp-sgx-externalities", + "itp-stf-interface", + "itp-stf-state-handler", + "itp-storage", + "itp-teerex-storage", + "itp-time-utils", + "itp-types", + "jsonrpc-core", + "parity-scale-codec", + "sgx_crypto_helper", + "sgx_tstd", + "sgx_types", + "sp-core", + "sp-runtime", + "sp-std", +] + +[[package]] +name = "itp-time-utils" +version = "0.9.0" +dependencies = [ + "sgx_tstd", +] + +[[package]] +name = "itp-top-pool" +version = "0.9.0" +dependencies = [ + "byteorder 1.4.3", + "derive_more", + "ita-stf", + "itc-direct-rpc-server", + "itp-types", + "its-primitives", + "jsonrpc-core", + "linked-hash-map", + "log", + "parity-scale-codec", + "serde 1.0.147", + "sgx_tstd", + "sgx_types", + "sp-application-crypto", + "sp-core", + "sp-runtime", + "thiserror 1.0.9", +] + +[[package]] +name = "itp-top-pool-author" +version = "0.9.0" +dependencies = [ + "derive_more", + "ita-stf", + "itp-enclave-metrics", + "itp-ocall-api", + "itp-sgx-crypto", + "itp-stf-state-handler", + "itp-test", + "itp-top-pool", + "itp-types", + "itp-utils", + "jsonrpc-core", + "log", + "parity-scale-codec", + "sgx_tstd", + "sgx_types", + "sp-core", + "sp-runtime", + "thiserror 1.0.9", +] + +[[package]] +name = "itp-types" +version = "0.9.0" +dependencies = [ + "chrono 0.4.23", + "frame-system", + "itp-sgx-runtime-primitives", + "pallet-balances", + "parity-scale-codec", + "primitive-types", + "serde 1.0.147", + "serde_json 1.0.89", + "sp-core", + "sp-runtime", + "sp-std", +] + +[[package]] +name = "itp-utils" +version = "0.9.0" +dependencies = [ + "frame-support", + "hex 0.4.3", + "parity-scale-codec", + "sgx_tstd", + "sp-core", + "thiserror 1.0.9", +] + +[[package]] +name = "its-block-composer" +version = "0.9.0" +dependencies = [ + "ita-stf", + "itp-node-api", + "itp-settings", + "itp-sgx-crypto", + "itp-sgx-externalities", + "itp-stf-executor", + "itp-stf-interface", + "itp-time-utils", + "itp-top-pool-author", + "itp-types", + "its-primitives", + "its-state", + "log", + "parity-scale-codec", + "sgx_tstd", + "sgx_types", + "sp-core", + "sp-runtime", + "thiserror 1.0.9", +] + +[[package]] +name = "its-block-verification" +version = "0.9.0" +dependencies = [ + "frame-support", + "itp-types", + "itp-utils", + "its-primitives", + "log", + "sgx_tstd", + "sp-consensus-slots", + "sp-core", + "sp-runtime", + "thiserror 1.0.9", +] + +[[package]] +name = "its-consensus-aura" +version = "0.9.0" +dependencies = [ + "finality-grandpa", + "frame-support", + "ita-stf", + "itc-parentchain-block-import-dispatcher", + "itp-enclave-metrics", + "itp-ocall-api", + "itp-settings", + "itp-sgx-crypto", + "itp-sgx-externalities", + "itp-stf-executor", + "itp-stf-state-handler", + "itp-time-utils", + "itp-top-pool-author", + "itp-types", + "itp-utils", + "its-block-composer", + "its-block-verification", + "its-consensus-common", + "its-consensus-slots", + "its-primitives", + "its-state", + "its-validateer-fetch", + "log", + "parity-scale-codec", + "sgx_tstd", + "sp-core", + "sp-runtime", +] + +[[package]] +name = "its-consensus-common" +version = "0.9.0" +dependencies = [ + "itc-parentchain-light-client", + "itp-block-import-queue", + "itp-extrinsics-factory", + "itp-node-api-metadata", + "itp-node-api-metadata-provider", + "itp-ocall-api", + "itp-settings", + "itp-sgx-crypto", + "itp-types", + "its-block-verification", + "its-primitives", + "its-state", + "log", + "parity-scale-codec", + "sgx_tstd", + "sgx_types", + "sp-runtime", + "thiserror 1.0.9", +] + +[[package]] +name = "its-consensus-slots" +version = "0.9.0" +dependencies = [ + "derive_more", + "itp-settings", + "itp-sgx-io", + "itp-time-utils", + "itp-types", + "its-block-verification", + "its-consensus-common", + "its-primitives", + "lazy_static", + "log", + "parity-scale-codec", + "sgx_tstd", + "sp-consensus-slots", + "sp-runtime", +] + +[[package]] +name = "its-primitives" +version = "0.1.0" +dependencies = [ + "parity-scale-codec", + "scale-info", + "serde 1.0.147", + "sp-core", + "sp-io", + "sp-runtime", + "sp-std", +] + +[[package]] +name = "its-rpc-handler" +version = "0.9.0" +dependencies = [ + "itp-rpc", + "itp-top-pool-author", + "itp-types", + "itp-utils", + "its-primitives", + "jsonrpc-core", + "log", + "parity-scale-codec", + "rust-base58", + "sgx_tstd", + "sgx_types", + "sp-core", +] + +[[package]] +name = "its-sidechain" +version = "0.9.0" +dependencies = [ + "its-block-composer", + "its-consensus-aura", + "its-consensus-common", + "its-consensus-slots", + "its-primitives", + "its-rpc-handler", + "its-state", + "its-validateer-fetch", +] + +[[package]] +name = "its-state" +version = "0.9.0" +dependencies = [ + "frame-support", + "itp-sgx-externalities", + "itp-storage", + "its-primitives", + "log", + "parity-scale-codec", + "serde 1.0.147", + "sgx_tstd", + "sp-core", + "sp-io", + "sp-std", + "thiserror 1.0.9", +] + +[[package]] +name = "its-validateer-fetch" +version = "0.9.0" +dependencies = [ + "derive_more", + "frame-support", + "itp-ocall-api", + "itp-storage", + "itp-teerex-storage", + "itp-types", + "parity-scale-codec", + "sp-core", + "sp-runtime", + "sp-std", + "thiserror 1.0.37", +] + +[[package]] +name = "js-sys" +version = "0.3.60" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49409df3e3bf0856b916e2ceaca09ee28e6871cf7d9ce97a692cacfdb2a25a47" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "jsonrpc-core" +version = "18.0.0" +source = "git+https://github.com/scs/jsonrpc?branch=no_std_v18#0faf53c491c3222b96242a973d902dd06e9b6674" +dependencies = [ + "futures 0.3.8", + "log", + "serde 1.0.118 (git+https://github.com/mesalock-linux/serde-sgx)", + "serde_derive 1.0.118", + "serde_json 1.0.60 (git+https://github.com/mesalock-linux/serde-json-sgx)", +] + +[[package]] +name = "k256" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19c3a5e0a0b8450278feda242592512e09f61c72e018b8cd5c859482802daf2d" +dependencies = [ + "cfg-if 1.0.0", + "ecdsa", + "elliptic-curve", + "sec1", +] + +[[package]] +name = "keccak" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3afef3b6eff9ce9d8ff9b3601125eec7f0c8cbac7abd14f355d053fa56c98768" +dependencies = [ + "cpufeatures", +] + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +dependencies = [ + "spin", +] + +[[package]] +name = "lazycell" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" + +[[package]] +name = "lc-assertion-build" +version = "0.1.0" +dependencies = [ + "frame-support", + "futures 0.3.8", + "hex 0.4.0", + "http", + "http_req", + "ita-stf", + "itc-rest-client", + "itp-ocall-api", + "itp-sgx-crypto", + "itp-sgx-externalities", + "itp-stf-executor", + "itp-storage", + "itp-top-pool-author", + "itp-types", + "itp-utils", + "lazy_static", + "lc-data-providers", + "lc-stf-task-sender", + "litentry-primitives", + "log", + "parity-scale-codec", + "serde 1.0.147", + "serde_json 1.0.89", + "sgx_tstd", + "sp-core", + "sp-io", + "sp-runtime", + "sp-std", + "thiserror 1.0.9", + "url", +] + +[[package]] +name = "lc-data-providers" +version = "0.1.0" +dependencies = [ + "hex 0.4.0", + "http", + "http_req", + "itc-rest-client", + "litentry-primitives", + "log", + "serde 1.0.147", + "serde_json 1.0.89", + "sgx_tstd", + "thiserror 1.0.9", + "url", +] + +[[package]] +name = "lc-identity-verification" +version = "0.1.0" +dependencies = [ + "frame-support", + "futures 0.3.8", + "hex 0.4.0", + "http", + "http_req", + "ita-stf", + "itc-rest-client", + "itp-ocall-api", + "itp-sgx-crypto", + "itp-sgx-externalities", + "itp-storage", + "itp-types", + "itp-utils", + "lazy_static", + "lc-data-providers", + "lc-stf-task-sender", + "litentry-primitives", + "log", + "parity-scale-codec", + "serde 1.0.147", + "serde_json 1.0.89", + "sgx_tstd", + "sp-core", + "sp-io", + "sp-std", + "thiserror 1.0.9", + "url", +] + +[[package]] +name = "lc-stf-task-receiver" +version = "0.1.0" +dependencies = [ + "frame-support", + "futures 0.3.8", + "hex 0.4.0", + "http", + "http_req", + "ita-sgx-runtime", + "ita-stf", + "itc-rest-client", + "itp-extrinsics-factory", + "itp-ocall-api", + "itp-sgx-crypto", + "itp-sgx-externalities", + "itp-stf-executor", + "itp-stf-state-handler", + "itp-stf-state-observer", + "itp-storage", + "itp-top-pool-author", + "itp-types", + "itp-utils", + "lazy_static", + "lc-assertion-build", + "lc-identity-verification", + "lc-stf-task-sender", + "litentry-primitives", + "log", + "parity-scale-codec", + "serde 1.0.147", + "serde_json 1.0.89", + "sgx_tstd", + "sp-core", + "sp-std", + "thiserror 1.0.9", + "url", +] + +[[package]] +name = "lc-stf-task-sender" +version = "0.1.0" +dependencies = [ + "http", + "http_req", + "itc-rest-client", + "itp-ocall-api", + "itp-sgx-crypto", + "itp-types", + "itp-utils", + "lazy_static", + "litentry-primitives", + "log", + "parity-scale-codec", + "serde 1.0.147", + "serde_json 1.0.89", + "sgx_tstd", + "sp-runtime", + "sp-std", + "thiserror 1.0.9", + "url", +] + +[[package]] +name = "libc" +version = "0.2.137" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc7fcc620a3bff7cdd7a365be3376c97191aeaccc2a603e600951e452615bf89" + +[[package]] +name = "libsecp256k1" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95b09eff1b35ed3b33b877ced3a691fc7a481919c7e29c53c906226fcf55e2a1" +dependencies = [ + "arrayref", + "base64 0.13.1", + "digest 0.9.0", + "libsecp256k1-core", + "libsecp256k1-gen-ecmult", + "libsecp256k1-gen-genmult", + "rand 0.8.5", + "serde 1.0.147", +] + +[[package]] +name = "libsecp256k1-core" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5be9b9bb642d8522a44d533eab56c16c738301965504753b03ad1de3425d5451" +dependencies = [ + "crunchy", + "digest 0.9.0", + "subtle", +] + +[[package]] +name = "libsecp256k1-gen-ecmult" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3038c808c55c87e8a172643a7d87187fc6c4174468159cb3090659d55bcb4809" +dependencies = [ + "libsecp256k1-core", +] + +[[package]] +name = "libsecp256k1-gen-genmult" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3db8d6ba2cec9eacc40e6e8ccc98931840301f1006e95647ceb2dd5c3aa06f7c" +dependencies = [ + "libsecp256k1-core", +] + +[[package]] +name = "linked-hash-map" +version = "0.5.2" +source = "git+https://github.com/mesalock-linux/linked-hash-map-sgx#03e763f7c251c16e0b85e2fb058ba47be52f2a49" +dependencies = [ + "sgx_tstd", +] + +[[package]] +name = "litentry-primitives" +version = "0.1.0" +dependencies = [ + "parity-scale-codec", + "primitives", + "scale-info", + "serde 1.0.147", + "serde_json 1.0.89", + "sgx_tstd", + "sp-core", + "sp-runtime", + "sp-std", +] + +[[package]] +name = "log" +version = "0.4.17" +source = "git+https://github.com/integritee-network/log-sgx#483383a9be3e2e900042eef9b6b2d0837411783f" +dependencies = [ + "cfg-if 1.0.0", + "sgx_tstd", +] + +[[package]] +name = "matches" +version = "0.1.8" +source = "git+https://github.com/mesalock-linux/rust-std-candidates-sgx#5747bcf37f3e18687758838da0339ff0f2c83924" + +[[package]] +name = "memchr" +version = "2.2.1" +source = "git+https://github.com/mesalock-linux/rust-memchr-sgx#fb51ee32766cb9a2be39b7fb2b5de26bb86dcdeb" +dependencies = [ + "sgx_libc", + "sgx_tstd", + "sgx_types", +] + +[[package]] +name = "memchr" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" + +[[package]] +name = "memory-db" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6566c70c1016f525ced45d7b7f97730a2bafb037c788211d0c186ef5b2189f0a" +dependencies = [ + "hash-db", + "hashbrown 0.12.3", + "parity-util-mem", +] + +[[package]] +name = "merlin" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e261cf0f8b3c42ded9f7d2bb59dea03aa52bc8a1cbc7482f9fc3fd1229d3b42" +dependencies = [ + "byteorder 1.4.3", + "keccak", + "rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", + "zeroize", +] + +[[package]] +name = "mio" +version = "0.6.21" +source = "git+https://github.com/mesalock-linux/mio-sgx?tag=sgx_1.1.3#5b0e56a3066231c7a8d1876c7be3a19b08ffdfd5" +dependencies = [ + "iovec", + "log", + "net2", + "sgx_libc", + "sgx_trts", + "sgx_tstd", + "slab 0.4.2", +] + +[[package]] +name = "mio-extras" +version = "2.0.6" +source = "git+https://github.com/integritee-network/mio-extras-sgx?rev=963234b#963234bf55e44f9efff921938255126c48deef3a" +dependencies = [ + "lazycell", + "log", + "mio", + "sgx_tstd", + "sgx_types", + "slab 0.4.7", +] + +[[package]] +name = "multibase" +version = "0.8.0" +source = "git+https://github.com/whalelephant/rust-multibase?branch=nstd#df67fb30e86998f7c10d4eea16a1cd480d2448c0" +dependencies = [ + "base-x", + "data-encoding", + "lazy_static", +] + +[[package]] +name = "multihash" +version = "0.11.4" +source = "git+https://github.com/whalelephant/rust-multihash?branch=nstd#2c8aca8fa1fcbcba26951d925de40fa81696020a" +dependencies = [ + "blake2b_simd", + "blake2s_simd", + "digest 0.9.0", + "sha-1", + "sha2 0.9.9", + "sha3 0.9.1", + "unsigned-varint", +] + +[[package]] +name = "net2" +version = "0.2.33" +source = "git+https://github.com/mesalock-linux/net2-rs-sgx#554583d15f3c9dff5d862a6ae64e227bb38fa729" +dependencies = [ + "cfg-if 0.1.10", + "sgx_libc", + "sgx_tstd", +] + +[[package]] +name = "nodrop" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72ef4a56884ca558e5ddb05a1d1e7e1bfd9a68d9ed024c21704cc98872dae1bb" + +[[package]] +name = "num" +version = "0.2.0" +source = "git+https://github.com/mesalock-linux/num-sgx#22645415542cc67551890dfdd34f4d5638b9ec78" +dependencies = [ + "num-bigint", + "num-complex", + "num-integer 0.1.41", + "num-iter", + "num-rational", + "num-traits 0.2.10", +] + +[[package]] +name = "num-bigint" +version = "0.2.5" +source = "git+https://github.com/mesalock-linux/num-bigint-sgx#76a5bed94dc31c32bd1670dbf72877abcf9bbc09" +dependencies = [ + "autocfg 1.1.0", + "num-integer 0.1.41", + "num-traits 0.2.10", + "sgx_tstd", +] + +[[package]] +name = "num-complex" +version = "0.2.3" +source = "git+https://github.com/mesalock-linux/num-complex-sgx#19700ad6de079ebc5560db472c282d1591e0d84f" +dependencies = [ + "autocfg 0.1.8", + "num-traits 0.2.10", + "sgx_tstd", +] + +[[package]] +name = "num-integer" +version = "0.1.41" +source = "git+https://github.com/mesalock-linux/num-integer-sgx#404c50e5378ca635261688b080dee328ff42b6bd" +dependencies = [ + "autocfg 0.1.8", + "num-traits 0.2.10", + "sgx_tstd", +] + +[[package]] +name = "num-integer" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" +dependencies = [ + "autocfg 1.1.0", + "num-traits 0.2.15", +] + +[[package]] +name = "num-iter" +version = "0.1.39" +source = "git+https://github.com/mesalock-linux/num-iter-sgx#f19fc44fcad0b82a040e5a24c511e5049cc04b60" +dependencies = [ + "num-integer 0.1.41", + "num-traits 0.2.10", + "sgx_tstd", +] + +[[package]] +name = "num-rational" +version = "0.2.2" +source = "git+https://github.com/mesalock-linux/num-rational-sgx#be65f9ce439f3c9ec850d8041635ab6c3309b816" +dependencies = [ + "autocfg 0.1.8", + "num-bigint", + "num-integer 0.1.41", + "num-traits 0.2.10", + "sgx_tstd", +] + +[[package]] +name = "num-traits" +version = "0.2.10" +source = "git+https://github.com/mesalock-linux/num-traits-sgx#af046e0b15c594c960007418097dd4ff37ec3f7a" +dependencies = [ + "autocfg 0.1.8", + "sgx_tstd", +] + +[[package]] +name = "num-traits" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" +dependencies = [ + "autocfg 1.1.0", +] + +[[package]] +name = "ofb" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5e609fc8b72da3dabd56427be9489d8a9f4bd2e4dc41660dd033c3c8e90b93c" +dependencies = [ + "cipher", +] + +[[package]] +name = "once_cell" +version = "1.4.0" +source = "git+https://github.com/mesalock-linux/once_cell-sgx#cefcaa03fed4d85276b3235d875f1b45d399cc3c" +dependencies = [ + "sgx_tstd", +] + +[[package]] +name = "once_cell" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86f0b0d4bf799edbc74508c1e8bf170ff5f41238e5f8225603ca7caaae2b7860" + +[[package]] +name = "opaque-debug" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2839e79665f131bdb5782e51f2c6c9599c133c6098982a54c794358bf432529c" + +[[package]] +name = "opaque-debug" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" + +[[package]] +name = "pallet-aura" +version = "4.0.0-dev" +source = "git+https://github.com/paritytech/substrate.git?branch=polkadot-v0.9.29#cc370aa61e15c18d23a2f686b812fd576a630afe" +dependencies = [ + "frame-support", + "frame-system", + "pallet-timestamp", + "parity-scale-codec", + "scale-info", + "sp-application-crypto", + "sp-consensus-aura", + "sp-runtime", + "sp-std", +] + +[[package]] +name = "pallet-authorship" +version = "4.0.0-dev" +source = "git+https://github.com/paritytech/substrate.git?branch=polkadot-v0.9.29#cc370aa61e15c18d23a2f686b812fd576a630afe" +dependencies = [ + "frame-support", + "frame-system", + "impl-trait-for-tuples", + "parity-scale-codec", + "scale-info", + "sp-authorship", + "sp-runtime", + "sp-std", +] + +[[package]] +name = "pallet-balances" +version = "4.0.0-dev" +source = "git+https://github.com/paritytech/substrate.git?branch=polkadot-v0.9.29#cc370aa61e15c18d23a2f686b812fd576a630afe" +dependencies = [ + "frame-support", + "frame-system", + "log", + "parity-scale-codec", + "scale-info", + "sp-runtime", + "sp-std", +] + +[[package]] +name = "pallet-evm" +version = "6.0.0-dev" +source = "git+https://github.com/integritee-network/frontier.git?branch=polkadot-v0.9.29#ee051c19f7dc7d54f0c092275db20d0a56a751af" +dependencies = [ + "evm", + "fp-evm", + "frame-support", + "frame-system", + "hex 0.4.3", + "log", + "pallet-timestamp", + "parity-scale-codec", + "primitive-types", + "rlp", + "scale-info", + "sp-core", + "sp-io", + "sp-runtime", + "sp-std", +] + +[[package]] +name = "pallet-grandpa" +version = "4.0.0-dev" +source = "git+https://github.com/paritytech/substrate.git?branch=polkadot-v0.9.29#cc370aa61e15c18d23a2f686b812fd576a630afe" +dependencies = [ + "frame-support", + "frame-system", + "log", + "pallet-authorship", + "pallet-session", + "parity-scale-codec", + "scale-info", + "sp-application-crypto", + "sp-core", + "sp-finality-grandpa", + "sp-io", + "sp-runtime", + "sp-session", + "sp-staking", + "sp-std", +] + +[[package]] +name = "pallet-identity-management" +version = "0.1.0" +dependencies = [ + "frame-support", + "frame-system", + "hex 0.4.3", + "litentry-primitives", + "log", + "parity-scale-codec", + "scale-info", + "serde 1.0.147", + "serde_derive 1.0.147", + "serde_json 1.0.89", + "sp-core", + "sp-io", + "sp-runtime", + "sp-std", +] + +[[package]] +name = "pallet-identity-management" +version = "0.1.0" +source = "git+https://github.com/litentry/litentry-parachain.git?branch=tee-dev#469f769c725ee058a9be22c055ebe5f359800493" +dependencies = [ + "frame-support", + "frame-system", + "hex 0.4.3", + "log", + "parity-scale-codec", + "primitives", + "scale-info", + "sp-arithmetic", + "sp-core", + "sp-runtime", + "sp-std", +] + +[[package]] +name = "pallet-parentchain" +version = "0.9.0" +source = "git+https://github.com/integritee-network/pallets.git?branch=master#23180717442b83cc4b4954ac72d42d0992830163" +dependencies = [ + "frame-support", + "frame-system", + "log", + "parity-scale-codec", + "scale-info", + "sp-core", + "sp-io", + "sp-runtime", + "sp-std", +] + +[[package]] +name = "pallet-randomness-collective-flip" +version = "4.0.0-dev" +source = "git+https://github.com/paritytech/substrate.git?branch=polkadot-v0.9.29#cc370aa61e15c18d23a2f686b812fd576a630afe" +dependencies = [ + "frame-support", + "frame-system", + "parity-scale-codec", + "safe-mix", + "scale-info", + "sp-runtime", + "sp-std", +] + +[[package]] +name = "pallet-session" +version = "4.0.0-dev" +source = "git+https://github.com/paritytech/substrate.git?branch=polkadot-v0.9.29#cc370aa61e15c18d23a2f686b812fd576a630afe" +dependencies = [ + "frame-support", + "frame-system", + "impl-trait-for-tuples", + "log", + "pallet-timestamp", + "parity-scale-codec", + "scale-info", + "sp-core", + "sp-io", + "sp-runtime", + "sp-session", + "sp-staking", + "sp-std", +] + +[[package]] +name = "pallet-sudo" +version = "4.0.0-dev" +source = "git+https://github.com/paritytech/substrate.git?branch=polkadot-v0.9.29#cc370aa61e15c18d23a2f686b812fd576a630afe" +dependencies = [ + "frame-support", + "frame-system", + "parity-scale-codec", + "scale-info", + "sp-io", + "sp-runtime", + "sp-std", +] + +[[package]] +name = "pallet-timestamp" +version = "4.0.0-dev" +source = "git+https://github.com/paritytech/substrate.git?branch=polkadot-v0.9.29#cc370aa61e15c18d23a2f686b812fd576a630afe" +dependencies = [ + "frame-support", + "frame-system", + "log", + "parity-scale-codec", + "scale-info", + "sp-inherents", + "sp-runtime", + "sp-std", + "sp-timestamp", +] + +[[package]] +name = "pallet-transaction-payment" +version = "4.0.0-dev" +source = "git+https://github.com/paritytech/substrate.git?branch=polkadot-v0.9.29#cc370aa61e15c18d23a2f686b812fd576a630afe" +dependencies = [ + "frame-support", + "frame-system", + "parity-scale-codec", + "scale-info", + "sp-core", + "sp-io", + "sp-runtime", + "sp-std", +] + +[[package]] +name = "pallet-transaction-payment-rpc-runtime-api" +version = "4.0.0-dev" +source = "git+https://github.com/paritytech/substrate.git?branch=polkadot-v0.9.29#cc370aa61e15c18d23a2f686b812fd576a630afe" +dependencies = [ + "pallet-transaction-payment", + "parity-scale-codec", + "sp-api", + "sp-runtime", +] + +[[package]] +name = "parity-scale-codec" +version = "3.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "366e44391a8af4cfd6002ef6ba072bae071a96aafca98d7d448a34c5dca38b6a" +dependencies = [ + "arrayvec 0.7.2", + "bitvec", + "byte-slice-cast", + "bytes 1.3.0", + "impl-trait-for-tuples", + "parity-scale-codec-derive", + "serde 1.0.147", +] + +[[package]] +name = "parity-scale-codec-derive" +version = "3.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9299338969a3d2f491d65f140b00ddec470858402f888af98e8642fb5e8965cd" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote 1.0.21", + "syn 1.0.103", +] + +[[package]] +name = "parity-util-mem" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c32561d248d352148124f036cac253a644685a21dc9fea383eb4907d7bd35a8f" +dependencies = [ + "cfg-if 1.0.0", + "hashbrown 0.12.3", + "impl-trait-for-tuples", + "parity-util-mem-derive", + "primitive-types", + "winapi", +] + +[[package]] +name = "parity-util-mem-derive" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f557c32c6d268a07c921471619c0295f5efad3a0e76d4f97a05c091a51d110b2" +dependencies = [ + "proc-macro2", + "syn 1.0.103", + "synstructure", +] + +[[package]] +name = "paste" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1de2e551fb905ac83f73f7aedf2f0cb4a0da7e35efa24a202a936269f1f18e1" + +[[package]] +name = "pem" +version = "0.8.2" +source = "git+https://github.com/mesalock-linux/pem-rs-sgx#fdfef4f24a9fb3fa72e8a71bb28bd8ff15feff2f" +dependencies = [ + "base64 0.13.0 (git+https://github.com/mesalock-linux/rust-base64-sgx)", + "once_cell 1.4.0", + "regex 1.3.1", + "sgx_tstd", +] + +[[package]] +name = "percent-encoding" +version = "2.1.0" +source = "git+https://github.com/mesalock-linux/rust-url-sgx?tag=sgx_1.1.3#23832f3191456c2d4a0faab10952e1747be58ca8" + +[[package]] +name = "pin-project-lite" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "postcard" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a25c0b0ae06fcffe600ad392aabfa535696c8973f2253d9ac83171924c58a858" +dependencies = [ + "postcard-cobs", + "serde 1.0.147", +] + +[[package]] +name = "postcard-cobs" +version = "0.1.5-pre" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c68cb38ed13fd7bc9dd5db8f165b7c8d9c1a315104083a2b10f11354c2af97f" + +[[package]] +name = "ppv-lite86" +version = "0.2.6" +source = "git+https://github.com/mesalock-linux/cryptocorrosion-sgx#32d7de50b5f03a10fe5a42167410be2dd3c2e389" + +[[package]] +name = "primitive-types" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e28720988bff275df1f51b171e1b2a18c30d194c4d2b61defdacecd625a5d94a" +dependencies = [ + "fixed-hash", + "impl-codec", + "impl-rlp", + "impl-serde", + "scale-info", + "uint", +] + +[[package]] +name = "primitives" +version = "0.9.11" +source = "git+https://github.com/litentry/litentry-parachain.git?branch=tee-dev#469f769c725ee058a9be22c055ebe5f359800493" +dependencies = [ + "parity-scale-codec", + "scale-info", + "sp-core", + "sp-runtime", + "sp-std", + "xcm", +] + +[[package]] +name = "proc-macro-crate" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eda0fc3b0fb7c975631757e14d9049da17374063edb6ebbcbc54d880d4fe94e9" +dependencies = [ + "once_cell 1.16.0", + "thiserror 1.0.37", + "toml", +] + +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote 1.0.21", + "syn 1.0.103", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote 1.0.21", + "version_check", +] + +[[package]] +name = "proc-macro-hack" +version = "0.5.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5" + +[[package]] +name = "proc-macro-nested" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc881b2c22681370c6a780e47af9840ef841837bc98118431d4e1868bd0c1086" + +[[package]] +name = "proc-macro2" +version = "1.0.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ea3d908b0e36316caf9e9e2c4625cdde190a7e6f440d794667ed17a1855e725" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quick-error" +version = "1.2.2" +source = "git+https://github.com/mesalock-linux/quick-error-sgx#468bf2cce746f34dd3df8c1c5b4a5a6494914d36" + +[[package]] +name = "quick-protobuf" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e489d4a83c17ea69b0291630229b5d4c92a94a3bf0165f7f72f506e94cda8b4b" +dependencies = [ + "byteorder 1.4.3", +] + +[[package]] +name = "quote" +version = "0.3.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a6e920b65c65f10b2ae65c831a81a073a89edd28c7cce89475bff467ab4167a" + +[[package]] +name = "quote" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbe448f377a7d6961e30f5955f9b8d106c3f5e449d493ee1b125c1d43c2b5179" +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.7.3" +source = "git+https://github.com/mesalock-linux/rand-sgx?tag=sgx_1.1.3#83583f073de3b4f75c3c3ef5e174d484ed941f85" +dependencies = [ + "getrandom 0.1.14", + "rand_chacha", + "rand_core 0.5.1 (git+https://github.com/mesalock-linux/rand-sgx?tag=sgx_1.1.3)", + "sgx_tstd", +] + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "rand_core 0.6.4", +] + +[[package]] +name = "rand_chacha" +version = "0.2.2" +source = "git+https://github.com/mesalock-linux/rand-sgx?tag=sgx_1.1.3#83583f073de3b4f75c3c3ef5e174d484ed941f85" +dependencies = [ + "ppv-lite86", + "rand_core 0.5.1 (git+https://github.com/mesalock-linux/rand-sgx?tag=sgx_1.1.3)", + "sgx_tstd", +] + +[[package]] +name = "rand_core" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" + +[[package]] +name = "rand_core" +version = "0.5.1" +source = "git+https://github.com/mesalock-linux/rand-sgx?tag=sgx_1.1.3#83583f073de3b4f75c3c3ef5e174d484ed941f85" +dependencies = [ + "getrandom 0.1.14", + "sgx_tstd", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" + +[[package]] +name = "rcgen" +version = "0.9.2" +source = "git+https://github.com/integritee-network/rcgen#1852c8dbeb74de36a422d218254b659497daf717" +dependencies = [ + "chrono 0.4.11", + "pem", + "ring 0.16.19", + "sgx_tstd", + "yasna", +] + +[[package]] +name = "ref-cast" +version = "1.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53b15debb4f9d60d767cd8ca9ef7abb2452922f3214671ff052defc7f3502c44" +dependencies = [ + "ref-cast-impl", +] + +[[package]] +name = "ref-cast-impl" +version = "1.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abfa8511e9e94fd3de6585a3d3cd00e01ed556dc9814829280af0e8dc72a8f36" +dependencies = [ + "proc-macro2", + "quote 1.0.21", + "syn 1.0.103", +] + +[[package]] +name = "regex" +version = "1.3.1" +source = "git+https://github.com/mesalock-linux/regex-sgx#76aef86f9836532d17764523d0fa23bb7d2e31cf" +dependencies = [ + "aho-corasick 0.7.10", + "memchr 2.2.1", + "regex-syntax 0.6.12", + "sgx_tstd", + "thread_local", +] + +[[package]] +name = "regex" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e076559ef8e241f2ae3479e36f97bd5741c0330689e217ad51ce2c76808b868a" +dependencies = [ + "aho-corasick 0.7.20", + "memchr 2.5.0", + "regex-syntax 0.6.28", +] + +[[package]] +name = "regex-syntax" +version = "0.6.12" +source = "git+https://github.com/mesalock-linux/regex-sgx#76aef86f9836532d17764523d0fa23bb7d2e31cf" +dependencies = [ + "sgx_tstd", +] + +[[package]] +name = "regex-syntax" +version = "0.6.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848" + +[[package]] +name = "rfc6979" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96ef608575f6392792f9ecf7890c00086591d29a83910939d430753f7c050525" +dependencies = [ + "crypto-bigint", + "hmac", + "zeroize", +] + +[[package]] +name = "ring" +version = "0.16.19" +source = "git+https://github.com/mesalock-linux/ring-sgx?tag=v0.16.5#844efe271ed78a399d803b2579f5f2424d543c9f" +dependencies = [ + "cc", + "sgx_tstd", + "spin", + "untrusted", +] + +[[package]] +name = "ring" +version = "0.16.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc" +dependencies = [ + "cc", + "libc", + "once_cell 1.16.0", + "spin", + "untrusted", + "web-sys", + "winapi", +] + +[[package]] +name = "rlp" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb919243f34364b6bd2fc10ef797edbfa75f33c252e7998527479c6d6b47e1ec" +dependencies = [ + "bytes 1.3.0", + "rustc-hex", +] + +[[package]] +name = "rlp-derive" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e33d7b2abe0c340d8797fe2907d3f20d3b5ea5908683618bfe80df7f621f672a" +dependencies = [ + "proc-macro2", + "quote 1.0.21", + "syn 1.0.103", +] + +[[package]] +name = "rust-base58" +version = "0.0.4" +source = "git+https://github.com/mesalock-linux/rust-base58-sgx?rev=sgx_1.1.3#13fb3e0a543690e6e19332f37ba85fd74c56cb2f" +dependencies = [ + "num", + "sgx_tstd", +] + +[[package]] +name = "rustc-hex" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e75f6a532d0fd9f7f13144f392b6ad56a32696bfcd9c78f797f16bbb6f072d6" + +[[package]] +name = "rustc_version" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" +dependencies = [ + "semver 0.9.0", +] + +[[package]] +name = "rustc_version" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" +dependencies = [ + "semver 1.0.14", +] + +[[package]] +name = "rustls" +version = "0.19.0" +source = "git+https://github.com/mesalock-linux/rustls?tag=sgx_1.1.3#95b5e79dc24b02f3ce424437eb9698509d0baf58" +dependencies = [ + "base64 0.13.0 (git+https://github.com/mesalock-linux/rust-base64-sgx)", + "log", + "ring 0.16.19", + "sct", + "sgx_tstd", + "webpki", +] + +[[package]] +name = "rustls" +version = "0.19.0" +source = "git+https://github.com/mesalock-linux/rustls?branch=mesalock_sgx#95b5e79dc24b02f3ce424437eb9698509d0baf58" +dependencies = [ + "base64 0.13.0 (git+https://github.com/mesalock-linux/rust-base64-sgx)", + "log", + "ring 0.16.19", + "sct", + "sgx_tstd", + "webpki", +] + +[[package]] +name = "rustls" +version = "0.19.0" +source = "git+https://github.com/mesalock-linux/rustls?rev=sgx_1.1.3#95b5e79dc24b02f3ce424437eb9698509d0baf58" +dependencies = [ + "base64 0.13.0 (git+https://github.com/mesalock-linux/rust-base64-sgx)", + "log", + "ring 0.16.19", + "sct", + "sgx_tstd", + "webpki", +] + +[[package]] +name = "ryu" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4501abdff3ae82a1c1b477a17252eb69cee9e66eb915c1abaa4f44d873df9f09" + +[[package]] +name = "safe-mix" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d3d055a2582e6b00ed7a31c1524040aa391092bf636328350813f3a0605215c" +dependencies = [ + "rustc_version 0.2.3", +] + +[[package]] +name = "scale-info" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88d8a765117b237ef233705cc2cc4c6a27fccd46eea6ef0c8c6dae5f3ef407f8" +dependencies = [ + "cfg-if 1.0.0", + "derive_more", + "parity-scale-codec", + "scale-info-derive", + "serde 1.0.147", +] + +[[package]] +name = "scale-info-derive" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdcd47b380d8c4541044e341dcd9475f55ba37ddc50c908d945fc036a8642496" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote 1.0.21", + "syn 1.0.103", +] + +[[package]] +name = "schnorrkel" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "021b403afe70d81eea68f6ea12f6b3c9588e5d536a94c3bf80f15e7faa267862" +dependencies = [ + "arrayref", + "arrayvec 0.5.2", + "curve25519-dalek 2.1.3", + "merlin", + "rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", + "sha2 0.8.2", + "subtle", + "zeroize", +] + +[[package]] +name = "sct" +version = "0.6.0" +source = "git+https://github.com/mesalock-linux/sct.rs?branch=mesalock_sgx#c4d859cca232e6c9d88ca12048df3bc26e1ed4ad" +dependencies = [ + "ring 0.16.19", + "sgx_tstd", + "untrusted", +] + +[[package]] +name = "sec1" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08da66b8b0965a5555b6bd6639e68ccba85e1e2506f5fbb089e93f8a04e1a2d1" +dependencies = [ + "der", + "generic-array 0.14.6", + "subtle", + "zeroize", +] + +[[package]] +name = "secp256k1" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff55dc09d460954e9ef2fa8a7ced735a964be9981fd50e870b2b3b0705e14964" +dependencies = [ + "secp256k1-sys", +] + +[[package]] +name = "secp256k1-sys" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83080e2c2fc1006e625be82e5d1eb6a43b7fd9578b617fcc55814daf286bba4b" +dependencies = [ + "cc", +] + +[[package]] +name = "secrecy" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9bd1c54ea06cfd2f6b63219704de0b9b4f72dcc2b8fdef820be6cd799780e91e" +dependencies = [ + "zeroize", +] + +[[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.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e25dfac463d778e353db5be2449d1cce89bd6fd23c9f1ea21310ce6e5a1b29c4" + +[[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.118" +source = "git+https://github.com/mesalock-linux/serde-sgx?tag=sgx_1.1.3#db0226f1d5d70fca6b96af2c285851502204e21c" +dependencies = [ + "sgx_tstd", +] + +[[package]] +name = "serde" +version = "1.0.118" +source = "git+https://github.com/mesalock-linux/serde-sgx#db0226f1d5d70fca6b96af2c285851502204e21c" +dependencies = [ + "serde_derive 1.0.118", + "sgx_tstd", +] + +[[package]] +name = "serde" +version = "1.0.147" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d193d69bae983fc11a79df82342761dfbf28a99fc8d203dca4c3c1b590948965" +dependencies = [ + "serde_derive 1.0.147", +] + +[[package]] +name = "serde-big-array" +version = "0.3.0" +source = "git+https://github.com/mesalock-linux/serde-big-array-sgx#94122c5167aee38b39b09a620a60db2c28cf7428" +dependencies = [ + "serde 1.0.118 (git+https://github.com/mesalock-linux/serde-sgx)", + "serde_derive 1.0.118", +] + +[[package]] +name = "serde_derive" +version = "1.0.118" +source = "git+https://github.com/mesalock-linux/serde-sgx#db0226f1d5d70fca6b96af2c285851502204e21c" +dependencies = [ + "proc-macro2", + "quote 1.0.21", + "syn 1.0.103", +] + +[[package]] +name = "serde_derive" +version = "1.0.147" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f1d362ca8fc9c3e3a7484440752472d68a6caa98f1ab81d99b5dfe517cec852" +dependencies = [ + "proc-macro2", + "quote 1.0.21", + "syn 1.0.103", +] + +[[package]] +name = "serde_json" +version = "1.0.60" +source = "git+https://github.com/mesalock-linux/serde-json-sgx?tag=sgx_1.1.3#380893814ad2a057758d825bab798aa117f7362a" +dependencies = [ + "itoa 0.4.5", + "ryu", + "serde 1.0.118 (git+https://github.com/mesalock-linux/serde-sgx)", + "sgx_tstd", +] + +[[package]] +name = "serde_json" +version = "1.0.60" +source = "git+https://github.com/mesalock-linux/serde-json-sgx#380893814ad2a057758d825bab798aa117f7362a" +dependencies = [ + "itoa 0.4.5", + "ryu", + "serde 1.0.118 (git+https://github.com/mesalock-linux/serde-sgx)", + "sgx_tstd", +] + +[[package]] +name = "serde_json" +version = "1.0.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "020ff22c755c2ed3f8cf162dbb41a7268d934702f3ed3631656ea597e08fc3db" +dependencies = [ + "itoa 1.0.4", + "ryu", + "serde 1.0.147", +] + +[[package]] +name = "sgx_alloc" +version = "1.1.6" +source = "git+https://github.com/apache/incubator-teaclave-sgx-sdk?branch=v1.1.6-testing#9c1bbd52f188f600a212b57c916124245da1b7fd" + +[[package]] +name = "sgx_backtrace_sys" +version = "1.1.6" +source = "git+https://github.com/apache/incubator-teaclave-sgx-sdk?branch=v1.1.6-testing#9c1bbd52f188f600a212b57c916124245da1b7fd" +dependencies = [ + "cc", + "sgx_build_helper", + "sgx_libc", +] + +[[package]] +name = "sgx_build_helper" +version = "1.1.6" +source = "git+https://github.com/apache/incubator-teaclave-sgx-sdk?branch=v1.1.6-testing#9c1bbd52f188f600a212b57c916124245da1b7fd" + +[[package]] +name = "sgx_crypto_helper" +version = "1.1.6" +source = "git+https://github.com/apache/incubator-teaclave-sgx-sdk?branch=v1.1.6-testing#9c1bbd52f188f600a212b57c916124245da1b7fd" +dependencies = [ + "itertools", + "serde 1.0.118 (git+https://github.com/mesalock-linux/serde-sgx)", + "serde-big-array", + "serde_derive 1.0.118", + "sgx_tcrypto", + "sgx_tstd", + "sgx_types", +] + +[[package]] +name = "sgx_demangle" +version = "1.1.6" +source = "git+https://github.com/apache/incubator-teaclave-sgx-sdk?branch=v1.1.6-testing#9c1bbd52f188f600a212b57c916124245da1b7fd" + +[[package]] +name = "sgx_libc" +version = "1.1.6" +source = "git+https://github.com/apache/incubator-teaclave-sgx-sdk?branch=v1.1.6-testing#9c1bbd52f188f600a212b57c916124245da1b7fd" +dependencies = [ + "sgx_types", +] + +[[package]] +name = "sgx_rand" +version = "1.1.6" +source = "git+https://github.com/apache/incubator-teaclave-sgx-sdk?branch=v1.1.6-testing#9c1bbd52f188f600a212b57c916124245da1b7fd" +dependencies = [ + "sgx_trts", + "sgx_tstd", + "sgx_types", +] + +[[package]] +name = "sgx_serialize" +version = "1.1.6" +source = "git+https://github.com/apache/incubator-teaclave-sgx-sdk?branch=v1.1.6-testing#9c1bbd52f188f600a212b57c916124245da1b7fd" +dependencies = [ + "sgx_tstd", +] + +[[package]] +name = "sgx_serialize_derive" +version = "1.1.6" +source = "git+https://github.com/apache/incubator-teaclave-sgx-sdk?branch=v1.1.6-testing#9c1bbd52f188f600a212b57c916124245da1b7fd" +dependencies = [ + "quote 0.3.15", + "sgx_serialize_derive_internals", + "syn 0.11.11", +] + +[[package]] +name = "sgx_serialize_derive_internals" +version = "1.1.6" +source = "git+https://github.com/apache/incubator-teaclave-sgx-sdk?branch=v1.1.6-testing#9c1bbd52f188f600a212b57c916124245da1b7fd" +dependencies = [ + "syn 0.11.11", +] + +[[package]] +name = "sgx_tcrypto" +version = "1.1.6" +source = "git+https://github.com/apache/incubator-teaclave-sgx-sdk?branch=v1.1.6-testing#9c1bbd52f188f600a212b57c916124245da1b7fd" +dependencies = [ + "sgx_types", +] + +[[package]] +name = "sgx_tcrypto_helper" +version = "1.1.6" +source = "git+https://github.com/apache/incubator-teaclave-sgx-sdk?branch=v1.1.6-testing#9c1bbd52f188f600a212b57c916124245da1b7fd" +dependencies = [ + "sgx_crypto_helper", +] + +[[package]] +name = "sgx_tprotected_fs" +version = "1.1.6" +source = "git+https://github.com/apache/incubator-teaclave-sgx-sdk?branch=v1.1.6-testing#9c1bbd52f188f600a212b57c916124245da1b7fd" +dependencies = [ + "sgx_trts", + "sgx_types", +] + +[[package]] +name = "sgx_trts" +version = "1.1.6" +source = "git+https://github.com/apache/incubator-teaclave-sgx-sdk?branch=v1.1.6-testing#9c1bbd52f188f600a212b57c916124245da1b7fd" +dependencies = [ + "sgx_libc", + "sgx_types", +] + +[[package]] +name = "sgx_tse" +version = "1.1.6" +source = "git+https://github.com/apache/incubator-teaclave-sgx-sdk?branch=v1.1.6-testing#9c1bbd52f188f600a212b57c916124245da1b7fd" +dependencies = [ + "sgx_types", +] + +[[package]] +name = "sgx_tseal" +version = "1.1.6" +source = "git+https://github.com/apache/incubator-teaclave-sgx-sdk?branch=v1.1.6-testing#9c1bbd52f188f600a212b57c916124245da1b7fd" +dependencies = [ + "sgx_tcrypto", + "sgx_trts", + "sgx_tse", + "sgx_types", +] + +[[package]] +name = "sgx_tstd" +version = "1.1.6" +source = "git+https://github.com/apache/incubator-teaclave-sgx-sdk?branch=v1.1.6-testing#9c1bbd52f188f600a212b57c916124245da1b7fd" +dependencies = [ + "hashbrown_tstd", + "sgx_alloc", + "sgx_backtrace_sys", + "sgx_demangle", + "sgx_libc", + "sgx_tprotected_fs", + "sgx_trts", + "sgx_types", + "sgx_unwind", +] + +[[package]] +name = "sgx_tunittest" +version = "1.1.6" +source = "git+https://github.com/apache/incubator-teaclave-sgx-sdk?branch=v1.1.6-testing#9c1bbd52f188f600a212b57c916124245da1b7fd" +dependencies = [ + "sgx_tstd", +] + +[[package]] +name = "sgx_types" +version = "1.1.6" +source = "git+https://github.com/apache/incubator-teaclave-sgx-sdk?branch=v1.1.6-testing#9c1bbd52f188f600a212b57c916124245da1b7fd" + +[[package]] +name = "sgx_unwind" +version = "1.1.6" +source = "git+https://github.com/apache/incubator-teaclave-sgx-sdk?branch=v1.1.6-testing#9c1bbd52f188f600a212b57c916124245da1b7fd" +dependencies = [ + "sgx_build_helper", +] + +[[package]] +name = "sha-1" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99cd6713db3cf16b6c84e06321e049a9b9f699826e16096d23bbcc44d15d51a6" +dependencies = [ + "block-buffer 0.9.0", + "cfg-if 1.0.0", + "cpufeatures", + "digest 0.9.0", + "opaque-debug 0.3.0", +] + +[[package]] +name = "sha1" +version = "0.6.0" +source = "git+https://github.com/mesalock-linux/rust-sha1-sgx?tag=sgx_1.1.3#482a4d489e860d63a21662aaea988f600f8e20a4" +dependencies = [ + "sgx_tstd", +] + +[[package]] +name = "sha2" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a256f46ea78a0c0d9ff00077504903ac881a1dafdc20da66545699e7776b3e69" +dependencies = [ + "block-buffer 0.7.3", + "digest 0.8.1", + "fake-simd", + "opaque-debug 0.2.3", +] + +[[package]] +name = "sha2" +version = "0.9.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d58a1e1bf39749807d89cf2d98ac2dfa0ff1cb3faa38fbb64dd88ac8013d800" +dependencies = [ + "block-buffer 0.9.0", + "cfg-if 1.0.0", + "cpufeatures", + "digest 0.9.0", + "opaque-debug 0.3.0", +] + +[[package]] +name = "sha2" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82e6b795fe2e3b1e845bafcb27aa35405c4d47cdfc92af5fc8d3002f76cebdc0" +dependencies = [ + "cfg-if 1.0.0", + "cpufeatures", + "digest 0.10.6", +] + +[[package]] +name = "sha3" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f81199417d4e5de3f04b1e871023acea7389672c4135918f05aa9cbf2f2fa809" +dependencies = [ + "block-buffer 0.9.0", + "digest 0.9.0", + "keccak", + "opaque-debug 0.3.0", +] + +[[package]] +name = "sha3" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bdf0c33fae925bdc080598b84bc15c55e7b9a4a43b3c704da051f977469691c9" +dependencies = [ + "digest 0.10.6", + "keccak", +] + +[[package]] +name = "signature" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02658e48d89f2bec991f9a78e69cfa4c316f8d6a6c4ec12fae1aeb263d486788" +dependencies = [ + "digest 0.9.0", + "rand_core 0.6.4", +] + +[[package]] +name = "slab" +version = "0.4.2" +source = "git+https://github.com/mesalock-linux/slab-sgx#0b0e6ec2abd588afd2f40fd082bc473d100d0f40" +dependencies = [ + "sgx_tstd", +] + +[[package]] +name = "slab" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4614a76b2a8be0058caa9dbbaf66d988527d86d003c11a94fbd335d7661edcef" +dependencies = [ + "autocfg 1.1.0", +] + +[[package]] +name = "smallvec" +version = "1.6.1" +source = "git+https://github.com/mesalock-linux/rust-smallvec-sgx#b5925f10aa5bc3370a0fb339140ee063f5a888dd" +dependencies = [ + "sgx_tstd", +] + +[[package]] +name = "smallvec" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" + +[[package]] +name = "sp-api" +version = "4.0.0-dev" +source = "git+https://github.com/paritytech/substrate.git?branch=polkadot-v0.9.29#cc370aa61e15c18d23a2f686b812fd576a630afe" +dependencies = [ + "log", + "parity-scale-codec", + "sp-api-proc-macro", + "sp-core", + "sp-runtime", + "sp-std", + "sp-version", +] + +[[package]] +name = "sp-api-proc-macro" +version = "4.0.0-dev" +source = "git+https://github.com/paritytech/substrate.git?branch=polkadot-v0.9.29#cc370aa61e15c18d23a2f686b812fd576a630afe" +dependencies = [ + "blake2", + "proc-macro-crate", + "proc-macro2", + "quote 1.0.21", + "syn 1.0.103", +] + +[[package]] +name = "sp-application-crypto" +version = "6.0.0" +source = "git+https://github.com/paritytech/substrate.git?branch=polkadot-v0.9.29#cc370aa61e15c18d23a2f686b812fd576a630afe" +dependencies = [ + "parity-scale-codec", + "scale-info", + "sp-core", + "sp-io", + "sp-std", +] + +[[package]] +name = "sp-arithmetic" +version = "5.0.0" +source = "git+https://github.com/paritytech/substrate.git?branch=polkadot-v0.9.29#cc370aa61e15c18d23a2f686b812fd576a630afe" +dependencies = [ + "integer-sqrt", + "num-traits 0.2.15", + "parity-scale-codec", + "scale-info", + "sp-debug-derive", + "sp-std", + "static_assertions", +] + +[[package]] +name = "sp-authorship" +version = "4.0.0-dev" +source = "git+https://github.com/paritytech/substrate.git?branch=polkadot-v0.9.29#cc370aa61e15c18d23a2f686b812fd576a630afe" +dependencies = [ + "parity-scale-codec", + "sp-inherents", + "sp-runtime", + "sp-std", +] + +[[package]] +name = "sp-block-builder" +version = "4.0.0-dev" +source = "git+https://github.com/paritytech/substrate.git?branch=polkadot-v0.9.29#cc370aa61e15c18d23a2f686b812fd576a630afe" +dependencies = [ + "parity-scale-codec", + "sp-api", + "sp-inherents", + "sp-runtime", + "sp-std", +] + +[[package]] +name = "sp-consensus-aura" +version = "0.10.0-dev" +source = "git+https://github.com/paritytech/substrate.git?branch=polkadot-v0.9.29#cc370aa61e15c18d23a2f686b812fd576a630afe" +dependencies = [ + "parity-scale-codec", + "scale-info", + "sp-api", + "sp-application-crypto", + "sp-consensus-slots", + "sp-inherents", + "sp-runtime", + "sp-std", + "sp-timestamp", +] + +[[package]] +name = "sp-consensus-slots" +version = "0.10.0-dev" +source = "git+https://github.com/paritytech/substrate.git?branch=polkadot-v0.9.29#cc370aa61e15c18d23a2f686b812fd576a630afe" +dependencies = [ + "parity-scale-codec", + "scale-info", + "sp-arithmetic", + "sp-runtime", + "sp-std", + "sp-timestamp", +] + +[[package]] +name = "sp-core" +version = "6.0.0" +source = "git+https://github.com/paritytech/substrate.git?branch=polkadot-v0.9.29#cc370aa61e15c18d23a2f686b812fd576a630afe" +dependencies = [ + "bitflags", + "blake2-rfc", + "byteorder 1.4.3", + "ed25519-zebra", + "hash-db", + "hash256-std-hasher", + "hex 0.4.3", + "libsecp256k1", + "log", + "merlin", + "num-traits 0.2.15", + "parity-scale-codec", + "parity-util-mem", + "primitive-types", + "scale-info", + "schnorrkel", + "secp256k1", + "secrecy", + "sp-core-hashing", + "sp-debug-derive", + "sp-runtime-interface", + "sp-std", + "sp-storage", + "ss58-registry", + "zeroize", +] + +[[package]] +name = "sp-core-hashing" +version = "4.0.0" +source = "git+https://github.com/paritytech/substrate.git?branch=polkadot-v0.9.29#cc370aa61e15c18d23a2f686b812fd576a630afe" +dependencies = [ + "blake2", + "byteorder 1.4.3", + "digest 0.10.6", + "sha2 0.10.6", + "sha3 0.10.6", + "sp-std", + "twox-hash", +] + +[[package]] +name = "sp-core-hashing-proc-macro" +version = "5.0.0" +source = "git+https://github.com/paritytech/substrate.git?branch=polkadot-v0.9.29#cc370aa61e15c18d23a2f686b812fd576a630afe" +dependencies = [ + "proc-macro2", + "quote 1.0.21", + "sp-core-hashing", + "syn 1.0.103", +] + +[[package]] +name = "sp-debug-derive" +version = "4.0.0" +source = "git+https://github.com/paritytech/substrate.git?branch=polkadot-v0.9.29#cc370aa61e15c18d23a2f686b812fd576a630afe" +dependencies = [ + "proc-macro2", + "quote 1.0.21", + "syn 1.0.103", +] + +[[package]] +name = "sp-externalities" +version = "0.12.0" +source = "git+https://github.com/paritytech/substrate.git?branch=polkadot-v0.9.29#cc370aa61e15c18d23a2f686b812fd576a630afe" +dependencies = [ + "environmental 1.1.3 (registry+https://github.com/rust-lang/crates.io-index)", + "parity-scale-codec", + "sp-std", + "sp-storage", +] + +[[package]] +name = "sp-finality-grandpa" +version = "4.0.0-dev" +source = "git+https://github.com/paritytech/substrate.git?branch=polkadot-v0.9.29#cc370aa61e15c18d23a2f686b812fd576a630afe" +dependencies = [ + "finality-grandpa", + "parity-scale-codec", + "scale-info", + "sp-api", + "sp-application-crypto", + "sp-core", + "sp-runtime", + "sp-std", +] + +[[package]] +name = "sp-inherents" +version = "4.0.0-dev" +source = "git+https://github.com/paritytech/substrate.git?branch=polkadot-v0.9.29#cc370aa61e15c18d23a2f686b812fd576a630afe" +dependencies = [ + "impl-trait-for-tuples", + "parity-scale-codec", + "sp-core", + "sp-std", +] + +[[package]] +name = "sp-io" +version = "6.0.0" +dependencies = [ + "environmental 1.1.3", + "hash-db", + "itp-sgx-externalities", + "libsecp256k1", + "log", + "parity-scale-codec", + "sgx_tstd", + "sgx_types", + "sp-core", + "sp-runtime-interface", + "sp-std", + "sp-tracing", + "sp-wasm-interface", + "tracing", + "tracing-core", +] + +[[package]] +name = "sp-offchain" +version = "4.0.0-dev" +source = "git+https://github.com/paritytech/substrate.git?branch=polkadot-v0.9.29#cc370aa61e15c18d23a2f686b812fd576a630afe" +dependencies = [ + "sp-api", + "sp-core", + "sp-runtime", +] + +[[package]] +name = "sp-runtime" +version = "6.0.0" +source = "git+https://github.com/paritytech/substrate.git?branch=polkadot-v0.9.29#cc370aa61e15c18d23a2f686b812fd576a630afe" +dependencies = [ + "either", + "hash256-std-hasher", + "impl-trait-for-tuples", + "log", + "parity-scale-codec", + "parity-util-mem", + "paste", + "scale-info", + "sp-application-crypto", + "sp-arithmetic", + "sp-core", + "sp-io", + "sp-std", +] + +[[package]] +name = "sp-runtime-interface" +version = "6.0.0" +source = "git+https://github.com/paritytech/substrate.git?branch=polkadot-v0.9.29#cc370aa61e15c18d23a2f686b812fd576a630afe" +dependencies = [ + "bytes 1.3.0", + "impl-trait-for-tuples", + "parity-scale-codec", + "primitive-types", + "sp-externalities", + "sp-runtime-interface-proc-macro", + "sp-std", + "sp-storage", + "sp-tracing", + "sp-wasm-interface", + "static_assertions", +] + +[[package]] +name = "sp-runtime-interface-proc-macro" +version = "5.0.0" +source = "git+https://github.com/paritytech/substrate.git?branch=polkadot-v0.9.29#cc370aa61e15c18d23a2f686b812fd576a630afe" +dependencies = [ + "Inflector", + "proc-macro-crate", + "proc-macro2", + "quote 1.0.21", + "syn 1.0.103", +] + +[[package]] +name = "sp-session" +version = "4.0.0-dev" +source = "git+https://github.com/paritytech/substrate.git?branch=polkadot-v0.9.29#cc370aa61e15c18d23a2f686b812fd576a630afe" +dependencies = [ + "parity-scale-codec", + "scale-info", + "sp-api", + "sp-core", + "sp-staking", + "sp-std", +] + +[[package]] +name = "sp-staking" +version = "4.0.0-dev" +source = "git+https://github.com/paritytech/substrate.git?branch=polkadot-v0.9.29#cc370aa61e15c18d23a2f686b812fd576a630afe" +dependencies = [ + "parity-scale-codec", + "scale-info", + "sp-runtime", + "sp-std", +] + +[[package]] +name = "sp-std" +version = "4.0.0" +source = "git+https://github.com/paritytech/substrate.git?branch=polkadot-v0.9.29#cc370aa61e15c18d23a2f686b812fd576a630afe" + +[[package]] +name = "sp-storage" +version = "6.0.0" +source = "git+https://github.com/paritytech/substrate.git?branch=polkadot-v0.9.29#cc370aa61e15c18d23a2f686b812fd576a630afe" +dependencies = [ + "parity-scale-codec", + "ref-cast", + "sp-debug-derive", + "sp-std", +] + +[[package]] +name = "sp-timestamp" +version = "4.0.0-dev" +source = "git+https://github.com/paritytech/substrate.git?branch=polkadot-v0.9.29#cc370aa61e15c18d23a2f686b812fd576a630afe" +dependencies = [ + "parity-scale-codec", + "sp-api", + "sp-inherents", + "sp-runtime", + "sp-std", +] + +[[package]] +name = "sp-tracing" +version = "5.0.0" +source = "git+https://github.com/paritytech/substrate.git?branch=polkadot-v0.9.29#cc370aa61e15c18d23a2f686b812fd576a630afe" +dependencies = [ + "parity-scale-codec", + "sp-std", + "tracing", + "tracing-core", +] + +[[package]] +name = "sp-transaction-pool" +version = "4.0.0-dev" +source = "git+https://github.com/paritytech/substrate.git?branch=polkadot-v0.9.29#cc370aa61e15c18d23a2f686b812fd576a630afe" +dependencies = [ + "sp-api", + "sp-runtime", +] + +[[package]] +name = "sp-trie" +version = "6.0.0" +source = "git+https://github.com/paritytech/substrate.git?branch=polkadot-v0.9.29#cc370aa61e15c18d23a2f686b812fd576a630afe" +dependencies = [ + "hash-db", + "memory-db", + "parity-scale-codec", + "scale-info", + "sp-core", + "sp-std", + "trie-db", + "trie-root", +] + +[[package]] +name = "sp-version" +version = "5.0.0" +source = "git+https://github.com/paritytech/substrate.git?branch=polkadot-v0.9.29#cc370aa61e15c18d23a2f686b812fd576a630afe" +dependencies = [ + "parity-scale-codec", + "scale-info", + "sp-core-hashing-proc-macro", + "sp-runtime", + "sp-std", + "sp-version-proc-macro", +] + +[[package]] +name = "sp-version-proc-macro" +version = "4.0.0-dev" +source = "git+https://github.com/paritytech/substrate.git?branch=polkadot-v0.9.29#cc370aa61e15c18d23a2f686b812fd576a630afe" +dependencies = [ + "parity-scale-codec", + "proc-macro2", + "quote 1.0.21", + "syn 1.0.103", +] + +[[package]] +name = "sp-wasm-interface" +version = "6.0.0" +source = "git+https://github.com/paritytech/substrate.git?branch=polkadot-v0.9.29#cc370aa61e15c18d23a2f686b812fd576a630afe" +dependencies = [ + "impl-trait-for-tuples", + "parity-scale-codec", + "sp-std", +] + +[[package]] +name = "spin" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" + +[[package]] +name = "ss58-registry" +version = "1.35.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa0813c10b9dbdc842c2305f949f724c64866e4ef4d09c9151e96f6a2106773c" +dependencies = [ + "Inflector", + "proc-macro2", + "quote 1.0.21", + "serde 1.0.147", + "serde_json 1.0.89", + "unicode-xid 0.2.4", +] + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "substrate-api-client" +version = "0.6.0" +source = "git+https://github.com/scs/substrate-api-client.git?branch=polkadot-v0.9.29#80e6d6daeb1ac8a4ce4968951bb2a4832fb054c0" +dependencies = [ + "ac-compose-macros", + "ac-node-api", + "ac-primitives", + "frame-metadata 15.0.0 (git+https://github.com/integritee-network/frame-metadata)", + "frame-support", + "hex 0.4.3", + "parity-scale-codec", + "sp-core", + "sp-runtime", + "sp-runtime-interface", + "sp-std", +] + +[[package]] +name = "substrate-fixed" +version = "0.5.9" +source = "git+https://github.com/encointer/substrate-fixed?tag=v0.5.9#a4fb461aae6205ffc55bed51254a40c52be04e5d" +dependencies = [ + "parity-scale-codec", + "scale-info", + "typenum 1.16.0", +] + +[[package]] +name = "subtle" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601" + +[[package]] +name = "syn" +version = "0.11.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3b891b9015c88c576343b9b3e41c2c11a51c219ef067b264bd9c8aa9b441dad" +dependencies = [ + "quote 0.3.15", + "synom", + "unicode-xid 0.0.4", +] + +[[package]] +name = "syn" +version = "1.0.103" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a864042229133ada95abf3b54fdc62ef5ccabe9515b64717bcb9a1919e59445d" +dependencies = [ + "proc-macro2", + "quote 1.0.21", + "unicode-ident", +] + +[[package]] +name = "synom" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a393066ed9010ebaed60b9eafa373d4b1baac186dd7e008555b0f702b51945b6" +dependencies = [ + "unicode-xid 0.0.4", +] + +[[package]] +name = "synstructure" +version = "0.12.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f36bdaa60a83aca3921b5259d5400cbf5e90fc51931376a9bd4a0eb79aa7210f" +dependencies = [ + "proc-macro2", + "quote 1.0.21", + "syn 1.0.103", + "unicode-xid 0.2.4", +] + +[[package]] +name = "tap" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" + +[[package]] +name = "termcolor" +version = "1.0.5" +source = "git+https://github.com/mesalock-linux/termcolor-sgx#fee5ac79b4a90197d646f3df5e1b45ac56be718b" +dependencies = [ + "sgx_tstd", +] + +[[package]] +name = "thiserror" +version = "1.0.9" +source = "git+https://github.com/mesalock-linux/thiserror-sgx?tag=sgx_1.1.3#c2f806b88616e06aab0af770366a76885d974fdc" +dependencies = [ + "sgx_tstd", + "thiserror-impl 1.0.9", +] + +[[package]] +name = "thiserror" +version = "1.0.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10deb33631e3c9018b9baf9dcbbc4f737320d2b576bac10f6aefa048fa407e3e" +dependencies = [ + "thiserror-impl 1.0.37", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.9" +source = "git+https://github.com/mesalock-linux/thiserror-sgx?tag=sgx_1.1.3#c2f806b88616e06aab0af770366a76885d974fdc" +dependencies = [ + "proc-macro2", + "quote 1.0.21", + "syn 1.0.103", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "982d17546b47146b28f7c22e3d08465f6b8903d0ea13c1660d9d84a6e7adcdbb" +dependencies = [ + "proc-macro2", + "quote 1.0.21", + "syn 1.0.103", +] + +[[package]] +name = "thread_local" +version = "1.0.0" +source = "git+https://github.com/mesalock-linux/thread_local-rs-sgx#a8e6e6ce280c53358f7b9e6febe534cba9950547" +dependencies = [ + "lazy_static", + "sgx_tstd", +] + +[[package]] +name = "tiny-keccak" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237" +dependencies = [ + "crunchy", +] + +[[package]] +name = "toml" +version = "0.5.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d82e1a7758622a465f8cee077614c73484dac5b836c02ff6a40d5d1010324d7" +dependencies = [ + "serde 1.0.147", +] + +[[package]] +name = "tracing" +version = "0.1.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8" +dependencies = [ + "cfg-if 1.0.0", + "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" + +[[package]] +name = "trie-db" +version = "0.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "004e1e8f92535694b4cb1444dc5a8073ecf0815e3357f729638b9f8fc4062908" +dependencies = [ + "hash-db", + "hashbrown 0.12.3", + "log", + "smallvec 1.10.0", +] + +[[package]] +name = "trie-root" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a36c5ca3911ed3c9a5416ee6c679042064b93fc637ded67e25f92e68d783891" +dependencies = [ + "hash-db", +] + +[[package]] +name = "triehash" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1631b201eb031b563d2e85ca18ec8092508e262a3196ce9bd10a67ec87b9f5c" +dependencies = [ + "hash-db", + "rlp", +] + +[[package]] +name = "tt-call" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e66dcbec4290c69dd03c57e76c2469ea5c7ce109c6dd4351c13055cf71ea055" + +[[package]] +name = "tungstenite" +version = "0.14.0" +source = "git+https://github.com/integritee-network/tungstenite-rs-sgx?branch=sgx-experimental#c87a2c08ea00897bb8b127ca0a5c30c3671492b0" +dependencies = [ + "base64 0.13.0 (git+https://github.com/mesalock-linux/rust-base64-sgx?tag=sgx_1.1.3)", + "byteorder 1.3.4", + "bytes 1.0.1", + "http", + "httparse", + "log", + "rand 0.7.3", + "rustls 0.19.0 (git+https://github.com/mesalock-linux/rustls?tag=sgx_1.1.3)", + "sgx_tstd", + "sha1", + "thiserror 1.0.9", + "url", + "utf-8", + "webpki", + "webpki-roots 0.21.0 (git+https://github.com/mesalock-linux/webpki-roots?tag=sgx_1.1.3)", +] + +[[package]] +name = "twox-hash" +version = "1.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97fee6b57c6a41524a810daee9286c02d7752c4253064d0b05472833a438f675" +dependencies = [ + "cfg-if 1.0.0", + "digest 0.10.6", + "static_assertions", +] + +[[package]] +name = "typenum" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987" + +[[package]] +name = "typenum" +version = "1.16.0" +source = "git+https://github.com/encointer/typenum?tag=v1.16.0#4c8dddaa8bdd13130149e43b4085ad14e960617f" +dependencies = [ + "parity-scale-codec", + "scale-info", +] + +[[package]] +name = "uint" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a45526d29728d135c2900b0d30573fe3ee79fceb12ef534c7bb30e810a91b601" +dependencies = [ + "byteorder 1.4.3", + "crunchy", + "hex 0.4.3", + "static_assertions", +] + +[[package]] +name = "unicase" +version = "2.6.0" +source = "git+https://github.com/mesalock-linux/unicase-sgx#0b0519348572927118af47af3da4da9ffdca8ec6" +dependencies = [ + "sgx_tstd", + "version_check", +] + +[[package]] +name = "unicode-bidi" +version = "0.3.4" +source = "git+https://github.com/mesalock-linux/unicode-bidi-sgx#eb10728a635a046e75747849fbc680cbbb7832c7" +dependencies = [ + "matches", + "sgx_tstd", +] + +[[package]] +name = "unicode-ident" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ceab39d59e4c9499d4e5a8ee0e2735b891bb7308ac83dfb4e80cad195c9f6f3" + +[[package]] +name = "unicode-normalization" +version = "0.1.12" +source = "git+https://github.com/mesalock-linux/unicode-normalization-sgx#c1b030611969f87d75782c1df77975167cbbd509" +dependencies = [ + "smallvec 1.6.1", +] + +[[package]] +name = "unicode-xid" +version = "0.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c1f860d7d29cf02cb2f3f359fd35991af3d30bac52c57d265a3c461074cb4dc" + +[[package]] +name = "unicode-xid" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" + +[[package]] +name = "unsigned-varint" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7fdeedbf205afadfe39ae559b75c3240f24e257d0ca27e85f85cb82aa19ac35" + +[[package]] +name = "untrusted" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" + +[[package]] +name = "url" +version = "2.1.1" +source = "git+https://github.com/mesalock-linux/rust-url-sgx?tag=sgx_1.1.3#23832f3191456c2d4a0faab10952e1747be58ca8" +dependencies = [ + "idna", + "matches", + "percent-encoding", + "sgx_tstd", +] + +[[package]] +name = "utf-8" +version = "0.7.4" +source = "git+https://github.com/integritee-network/rust-utf8-sgx?branch=sgx-experimental#b026700da83a2f00f0e9f36f813ef28e447a719e" +dependencies = [ + "sgx_tstd", +] + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "wasm-bindgen" +version = "0.2.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eaf9f5aceeec8be17c128b2e93e031fb8a4d469bb9c4ae2d7dc1888b26887268" +dependencies = [ + "cfg-if 1.0.0", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c8ffb332579b0557b52d268b91feab8df3615f265d5270fec2a8c95b17c1142" +dependencies = [ + "bumpalo", + "log", + "once_cell 1.16.0", + "proc-macro2", + "quote 1.0.21", + "syn 1.0.103", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "052be0f94026e6cbc75cdefc9bae13fd6052cdcaf532fa6c45e7ae33a1e6c810" +dependencies = [ + "quote 1.0.21", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07bc0c051dc5f23e307b13285f9d75df86bfdf816c5721e573dec1f9b8aa193c" +dependencies = [ + "proc-macro2", + "quote 1.0.21", + "syn 1.0.103", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c38c045535d93ec4f0b4defec448e4291638ee608530863b1e2ba115d4fff7f" + +[[package]] +name = "web-sys" +version = "0.3.60" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bcda906d8be16e728fd5adc5b729afad4e444e106ab28cd1c7256e54fa61510f" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "webpki" +version = "0.21.4" +source = "git+https://github.com/mesalock-linux/webpki?branch=mesalock_sgx#8dbe6fbeefadf05582ae47c7fa818b04db49c61e" +dependencies = [ + "ring 0.16.19", + "sgx_tstd", + "untrusted", +] + +[[package]] +name = "webpki-roots" +version = "0.21.0" +source = "git+https://github.com/mesalock-linux/webpki-roots?tag=sgx_1.1.3#6ff3be547ac13ccd46ae55605ad6506ce30688ef" +dependencies = [ + "sgx_tstd", + "webpki", +] + +[[package]] +name = "webpki-roots" +version = "0.21.0" +source = "git+https://github.com/mesalock-linux/webpki-roots?branch=mesalock_sgx#6ff3be547ac13ccd46ae55605ad6506ce30688ef" +dependencies = [ + "sgx_tstd", + "webpki", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "wyz" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" +dependencies = [ + "tap", +] + +[[package]] +name = "xcm" +version = "0.9.29" +source = "git+https://github.com/paritytech/polkadot?branch=release-v0.9.29#94078b44fb6c9767bf60ffcaaa3be40681be5a76" +dependencies = [ + "derivative", + "impl-trait-for-tuples", + "log", + "parity-scale-codec", + "scale-info", + "sp-runtime", + "xcm-procedural", +] + +[[package]] +name = "xcm-procedural" +version = "0.9.29" +source = "git+https://github.com/paritytech/polkadot?branch=release-v0.9.29#94078b44fb6c9767bf60ffcaaa3be40681be5a76" +dependencies = [ + "Inflector", + "proc-macro2", + "quote 1.0.21", + "syn 1.0.103", +] + +[[package]] +name = "yasna" +version = "0.3.1" +source = "git+https://github.com/mesalock-linux/yasna.rs-sgx?rev=sgx_1.1.3#a1f50714cd3eb29608ecf7888cacedc173edfdb2" +dependencies = [ + "bit-vec", + "chrono 0.4.11", + "num-bigint", + "sgx_tstd", +] + +[[package]] +name = "zeroize" +version = "1.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c394b5bd0c6f669e7275d9c20aa90ae064cb22e75a1cad54e1b34088034b149f" +dependencies = [ + "zeroize_derive", +] + +[[package]] +name = "zeroize_derive" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f8f187641dad4f680d25c4bfc4225b418165984179f26ca76ec4fb6441d3a17" +dependencies = [ + "proc-macro2", + "quote 1.0.21", + "syn 1.0.103", + "synstructure", +] diff --git a/tee-worker/enclave-runtime/Cargo.toml b/tee-worker/enclave-runtime/Cargo.toml new file mode 100644 index 0000000000..718dd6af1a --- /dev/null +++ b/tee-worker/enclave-runtime/Cargo.toml @@ -0,0 +1,184 @@ +[package] +authors = ["Integritee AG "] +edition = "2021" +name = "enclave-runtime" +version = "0.9.0" + +[workspace] +members = [] + +[lib] +crate-type = ["staticlib"] +name = "enclave_runtime" + +[features] +default = [] +evm = [ + "ita-sgx-runtime/evm", + "ita-stf/evm", +] +mockserver = [ + "ita-stf/mockserver", + "lc-stf-task-receiver/mockserver", +] +offchain-worker = [ + "itp-settings/offchain-worker", + "itp-top-pool-author/offchain-worker", +] +production = ["itp-settings/production"] +sidechain = ["itp-settings/sidechain", "itp-top-pool-author/sidechain"] +teeracle = [ + "ita-exchange-oracle", + "itp-settings/teeracle", + "itp-top-pool-author/teeracle", +] +test = [ + "ita-stf/test", + "itc-parentchain/mocks", + "itp-attestation-handler/test", + "itp-extrinsics-factory/mocks", + "itp-sgx-crypto/mocks", + "itp-stf-executor/test", + "itp-stf-executor/mocks", + "itp-stf-state-handler/test", + "itp-stf-state-observer/mocks", + "itp-storage/test", + "itp-test/sgx", + "itp-top-pool-author/test", + "itp-top-pool-author/mocks", + # substrate + "frame-system", +] + +[target.'cfg(not(target_env = "sgx"))'.dependencies] +sgx-crypto-helper = { branch = "master", git = "https://github.com/apache/teaclave-sgx-sdk.git", package = "sgx_tcrypto_helper" } +sgx_rand = { branch = "master", git = "https://github.com/apache/teaclave-sgx-sdk.git" } +sgx_serialize = { branch = "master", git = "https://github.com/apache/teaclave-sgx-sdk.git" } +sgx_serialize_derive = { branch = "master", git = "https://github.com/apache/teaclave-sgx-sdk.git" } +sgx_tcrypto = { branch = "master", git = "https://github.com/apache/teaclave-sgx-sdk.git" } +sgx_trts = { branch = "master", git = "https://github.com/apache/teaclave-sgx-sdk.git" } +sgx_tse = { branch = "master", git = "https://github.com/apache/teaclave-sgx-sdk.git" } +sgx_tseal = { branch = "master", git = "https://github.com/apache/teaclave-sgx-sdk.git" } +sgx_tstd = { branch = "master", git = "https://github.com/apache/teaclave-sgx-sdk.git", features = ["untrusted_fs", "net", "backtrace"] } +sgx_tunittest = { branch = "master", git = "https://github.com/apache/teaclave-sgx-sdk.git" } +sgx_types = { branch = "master", git = "https://github.com/apache/teaclave-sgx-sdk.git" } + +[dependencies] +codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } +derive_more = { version = "0.99.5" } +hex = { version = "0.4.3", default-features = false, features = ["alloc"] } +ipfs-unixfs = { default-features = false, git = "https://github.com/whalelephant/rust-ipfs", branch = "w-nstd" } +lazy_static = { version = "1.1.0", features = ["spin_no_std"] } +primitive-types = { version = "0.11.1", default-features = false, features = ["codec", "serde_no_std"] } + +# scs / integritee +jsonrpc-core = { default-features = false, git = "https://github.com/scs/jsonrpc", branch = "no_std_v18" } +substrate-api-client = { default-features = false, git = "https://github.com/scs/substrate-api-client.git", branch = "polkadot-v0.9.29" } + +# mesalock +env_logger = { git = "https://github.com/integritee-network/env_logger-sgx" } +log = { git = "https://github.com/integritee-network/log-sgx" } +rustls = { rev = "sgx_1.1.3", features = ["dangerous_configuration"], git = "https://github.com/mesalock-linux/rustls" } +serde = { tag = "sgx_1.1.3", git = "https://github.com/mesalock-linux/serde-sgx", features = ["alloc", "mesalock_sgx"] } +serde_derive = { git = "https://github.com/mesalock-linux/serde-sgx" } +serde_json = { tag = "sgx_1.1.3", git = "https://github.com/mesalock-linux/serde-json-sgx" } +webpki = { git = "https://github.com/mesalock-linux/webpki", branch = "mesalock_sgx" } + +# for attestation +base58 = { rev = "sgx_1.1.3", package = "rust-base58", default-features = false, features = ["mesalock_sgx"], git = "https://github.com/mesalock-linux/rust-base58-sgx" } + +cid = { default-features = false, git = "https://github.com/whalelephant/rust-cid", branch = "nstd" } +multibase = { default-features = false, git = "https://github.com/whalelephant/rust-multibase", branch = "nstd" } + +# local deps +ita-exchange-oracle = { path = "../app-libs/exchange-oracle", default-features = false, optional = true, features = ["sgx"] } +ita-sgx-runtime = { path = "../app-libs/sgx-runtime", default-features = false } +ita-stf = { path = "../app-libs/stf", default-features = false, features = ["sgx"] } +itc-direct-rpc-server = { path = "../core/direct-rpc-server", default-features = false, features = ["sgx"] } +itc-offchain-worker-executor = { path = "../core/offchain-worker-executor", default-features = false, features = ["sgx"] } +itc-parentchain = { path = "../core/parentchain/parentchain-crate", default-features = false, features = ["sgx"] } +itc-parentchain-test = { path = "../core/parentchain/test", default-features = false } +itc-tls-websocket-server = { path = "../core/tls-websocket-server", default-features = false, features = ["sgx"] } +itp-attestation-handler = { path = "../core-primitives/attestation-handler", default-features = false, features = ["sgx"] } +itp-block-import-queue = { path = "../core-primitives/block-import-queue", default-features = false, features = ["sgx"] } +itp-component-container = { path = "../core-primitives/component-container", default-features = false, features = ["sgx"] } +itp-extrinsics-factory = { path = "../core-primitives/extrinsics-factory", default-features = false, features = ["sgx"] } +itp-hashing = { path = "../core-primitives/hashing", default-features = false } +itp-node-api = { path = "../core-primitives/node-api", default-features = false, features = ["sgx"] } +itp-nonce-cache = { path = "../core-primitives/nonce-cache", default-features = false, features = ["sgx"] } +itp-ocall-api = { path = "../core-primitives/ocall-api", default-features = false } +itp-primitives-cache = { path = "../core-primitives/primitives-cache", default-features = false, features = ["sgx"] } +itp-rpc = { path = "../core-primitives/rpc", default-features = false, features = ["sgx"] } +itp-settings = { path = "../core-primitives/settings" } +itp-sgx-crypto = { path = "../core-primitives/sgx/crypto", default-features = false, features = ["sgx"] } +itp-sgx-externalities = { path = "../core-primitives/substrate-sgx/externalities", default-features = false, features = ["sgx"] } +itp-sgx-io = { path = "../core-primitives/sgx/io", default-features = false, features = ["sgx"] } +itp-stf-executor = { path = "../core-primitives/stf-executor", default-features = false, features = ["sgx"] } +itp-stf-interface = { path = "../core-primitives/stf-interface", default-features = false } +itp-stf-state-handler = { path = "../core-primitives/stf-state-handler", default-features = false, features = ["sgx"] } +itp-stf-state-observer = { path = "../core-primitives/stf-state-observer", default-features = false, features = ["sgx"] } +itp-storage = { path = "../core-primitives/storage", default-features = false, features = ["sgx"] } +itp-teerex-storage = { path = "../core-primitives/teerex-storage", default-features = false } +itp-test = { path = "../core-primitives/test", default-features = false, optional = true } +itp-time-utils = { path = "../core-primitives/time-utils", default-features = false, features = ["sgx"] } +itp-top-pool = { path = "../core-primitives/top-pool", default-features = false, features = ["sgx"] } +itp-top-pool-author = { path = "../core-primitives/top-pool-author", default-features = false, features = ["sgx"] } +itp-types = { path = "../core-primitives/types", default-features = false } +itp-utils = { path = "../core-primitives/utils", default-features = false, features = ["sgx"] } +its-block-verification = { path = "../sidechain/block-verification", default-features = false } +its-primitives = { path = "../sidechain/primitives", default-features = false } +its-sidechain = { path = "../sidechain/sidechain-crate", default-features = false, features = ["sgx"] } + +# litentry +lc-stf-task-receiver = { path = "../litentry/core/stf-task/receiver", default-features = false, features = ["sgx"] } +lc-stf-task-sender = { path = "../litentry/core/stf-task/sender", default-features = false, features = ["sgx"] } +litentry-primitives = { path = "../litentry/primitives", default-features = false, features = ["sgx"] } + +# substrate deps +frame-support = { default-features = false, git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.29" } +frame-system = { optional = true, default-features = false, git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.29" } +sp-core = { default-features = false, features = ["full_crypto"], git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.29" } +sp-runtime = { default-features = false, git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.29" } +sp-std = { default-features = false, git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.29" } + +[patch.crates-io] +env_logger = { git = "https://github.com/integritee-network/env_logger-sgx" } +getrandom = { git = "https://github.com/integritee-network/getrandom-sgx", branch = "update-v2.3" } +log = { git = "https://github.com/integritee-network/log-sgx" } + +[patch."https://github.com/mesalock-linux/log-sgx"] +log = { git = "https://github.com/integritee-network/log-sgx" } + +[patch."https://github.com/paritytech/substrate"] +sp-io = { path = "../core-primitives/substrate-sgx/sp-io" } + +#[patch."https://github.com/integritee-network/frontier"] +#pallet-evm = { path = "../../frontier/frame/evm"} + +[patch."https://github.com/apache/teaclave-sgx-sdk.git"] +sgx_alloc = { version = "1.1.6", git = "https://github.com/apache/incubator-teaclave-sgx-sdk", branch = "v1.1.6-testing" } +sgx_crypto_helper = { version = "1.1.6", git = "https://github.com/apache/incubator-teaclave-sgx-sdk", branch = "v1.1.6-testing" } +sgx_libc = { version = "1.1.6", git = "https://github.com/apache/incubator-teaclave-sgx-sdk", branch = "v1.1.6-testing" } +sgx_rand = { version = "1.1.6", git = "https://github.com/apache/incubator-teaclave-sgx-sdk", branch = "v1.1.6-testing" } +sgx_serialize = { version = "1.1.6", git = "https://github.com/apache/incubator-teaclave-sgx-sdk", branch = "v1.1.6-testing" } +sgx_serialize_derive = { version = "1.1.6", git = "https://github.com/apache/incubator-teaclave-sgx-sdk", branch = "v1.1.6-testing" } +sgx_serialize_derive_internals = { version = "1.1.6", git = "https://github.com/apache/incubator-teaclave-sgx-sdk", branch = "v1.1.6-testing" } +sgx_tcrypto = { version = "1.1.6", git = "https://github.com/apache/incubator-teaclave-sgx-sdk", branch = "v1.1.6-testing" } +sgx_tcrypto_helper = { version = "1.1.6", git = "https://github.com/apache/incubator-teaclave-sgx-sdk", branch = "v1.1.6-testing" } +sgx_trts = { version = "1.1.6", git = "https://github.com/apache/incubator-teaclave-sgx-sdk", branch = "v1.1.6-testing" } +sgx_tse = { version = "1.1.6", git = "https://github.com/apache/incubator-teaclave-sgx-sdk", branch = "v1.1.6-testing" } +sgx_tseal = { version = "1.1.6", git = "https://github.com/apache/incubator-teaclave-sgx-sdk", branch = "v1.1.6-testing" } +sgx_tstd = { version = "1.1.6", git = "https://github.com/apache/incubator-teaclave-sgx-sdk", branch = "v1.1.6-testing" } +sgx_tunittest = { version = "1.1.6", git = "https://github.com/apache/incubator-teaclave-sgx-sdk", branch = "v1.1.6-testing" } +sgx_types = { version = "1.1.6", git = "https://github.com/apache/incubator-teaclave-sgx-sdk", branch = "v1.1.6-testing" } + +#[patch."https://github.com/scs/substrate-api-client"] +#substrate-api-client = { path = "../../../scs/substrate-api-client" } + +#[patch."https://github.com/integritee-network/pallets.git"] +#pallet-parentchain = { path = "../../pallets/parentchain" } +#itp-types = { path = "../../pallets/primitives/types" } +#itp-utils = { path = "../../pallets/primitives/utils" } + +#[patch."https://github.com/integritee-network/http_req"] +#http_req-sgx = { package = "http_req", path = '../../http_req' } diff --git a/tee-worker/enclave-runtime/Enclave.config.production.xml b/tee-worker/enclave-runtime/Enclave.config.production.xml new file mode 100644 index 0000000000..9ea98e8fea --- /dev/null +++ b/tee-worker/enclave-runtime/Enclave.config.production.xml @@ -0,0 +1,12 @@ + + + 0 + 0 + 0x40000 + 0x20000000 + 8 + 0 + 1 + 0 + 0xFFFFFFFF + diff --git a/tee-worker/enclave-runtime/Enclave.config.xml b/tee-worker/enclave-runtime/Enclave.config.xml new file mode 100644 index 0000000000..fb02444fc1 --- /dev/null +++ b/tee-worker/enclave-runtime/Enclave.config.xml @@ -0,0 +1,12 @@ + + + 0 + 0 + 0x40000 + 0x20000000 + 16 + 0 + 0 + 0 + 0xFFFFFFFF + diff --git a/tee-worker/enclave-runtime/Enclave.edl b/tee-worker/enclave-runtime/Enclave.edl new file mode 100644 index 0000000000..d9dcc16c98 --- /dev/null +++ b/tee-worker/enclave-runtime/Enclave.edl @@ -0,0 +1,172 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +enclave { + from "sgx_backtrace.edl" import *; + from "sgx_tstd.edl" import *; + from "sgx_stdio.edl" import *; + from "sgx_backtrace.edl" import *; + from "sgx_tstdc.edl" import *; + from "sgx_tprotected_fs.edl" import *; + from "sgx_fs.edl" import *; + from "sgx_net.edl" import *; + from "sgx_time.edl" import *; + from "sgx_env.edl" import *; + from "sgx_thread.edl" import *; + from "sgx_pipe.edl" import *; + + include "sgx_quote.h" + + trusted { + /* define ECALLs here. */ + public sgx_status_t init( + [in, size=mu_ra_addr_size] uint8_t* mu_ra_addr, uint32_t mu_ra_addr_size, + [in, size=untrusted_worker_addr_size] uint8_t* untrusted_worker_addr, uint32_t untrusted_worker_addr_size + ); + + public sgx_status_t init_enclave_sidechain_components(); + + public sgx_status_t init_direct_invocation_server( + [in, size=server_addr_size] uint8_t* server_addr, uint32_t server_addr_size + ); + + public sgx_status_t init_parentchain_components( + [in, size=params_size] uint8_t* params, size_t params_size, + [out, size=latest_header_size] uint8_t* latest_header, size_t latest_header_size + ); + + public sgx_status_t init_shard( + [in, size=shard_size] uint8_t* shard, uint32_t shard_size + ); + + public sgx_status_t trigger_parentchain_block_import(); + + public sgx_status_t execute_trusted_calls(); + + public sgx_status_t sync_parentchain( + [in, size=blocks_size] uint8_t* blocks, size_t blocks_size, + [in] uint32_t* nonce + ); + + public sgx_status_t set_nonce( + [in] uint32_t* nonce + ); + + public sgx_status_t set_node_metadata( + [in, size=node_metadata_size] uint8_t* node_metadata, uint32_t node_metadata_size + ); + + public sgx_status_t get_rsa_encryption_pubkey( + [out, size=pubkey_size] uint8_t* pubkey, uint32_t pubkey_size); + + public sgx_status_t get_ecc_signing_pubkey( + [out, size=pubkey_size] uint8_t* pubkey, uint32_t pubkey_size); + + public sgx_status_t get_mrenclave( + [out, size=mrenclave_size] uint8_t* mrenclave, uint32_t mrenclave_size); + + public sgx_status_t perform_ra( + [in, size=w_url_size] uint8_t* w_url, uint32_t w_url_size, + [out, size=unchecked_extrinsic_size] uint8_t* unchecked_extrinsic, uint32_t unchecked_extrinsic_size, + int skip_ra + ); + + public sgx_status_t update_market_data_xt( + [in, size=crypto_currency_size] uint8_t* crypto_currency, uint32_t crypto_currency_size, + [in, size=fiat_currency_size] uint8_t* fiat_currency, uint32_t fiat_currency_size, + [out, size=unchecked_extrinsic_size] uint8_t* unchecked_extrinsic, uint32_t unchecked_extrinsic_size + ); + + public sgx_status_t dump_ra_to_disk(); + + public sgx_status_t run_state_provisioning_server(int fd, sgx_quote_sign_type_t quote_type, int skip_ra); + public sgx_status_t request_state_provisioning( + int fd, + sgx_quote_sign_type_t quote_type, + [in, size=shard_size] uint8_t* shard, uint32_t shard_size, + int skip_ra + ); + + public sgx_status_t call_rpc_methods( + [in, size=request_len] uint8_t* request, uint32_t request_len, + [out, size=response_len] uint8_t* response, uint32_t response_len + ); + + public size_t test_main_entrance(); + + public size_t run_stf_task_handler(); + }; + + untrusted { + sgx_status_t ocall_sgx_init_quote( + [out] sgx_target_info_t *ret_ti, + [out] sgx_epid_group_id_t *ret_gid + ); + + sgx_status_t ocall_get_ias_socket([out] int *ret_fd); + + sgx_status_t ocall_get_quote( + [in, size = sigrl_len] uint8_t * p_sigrl, uint32_t sigrl_len, + [in] sgx_report_t *report, sgx_quote_sign_type_t quote_type, + [in] sgx_spid_t *p_spid, [in] sgx_quote_nonce_t *p_nonce, + [out] sgx_report_t *p_qe_report, + [out, size = maxlen] sgx_quote_t *p_quote, uint32_t maxlen, + [out] uint32_t* p_quote_len + ); + + sgx_status_t ocall_get_update_info( + [in] sgx_platform_info_t * platformBlob, int32_t enclaveTrusted, + [out] sgx_update_info_bit_t * update_info + ); + + sgx_status_t ocall_read_ipfs( + [in, size = cid_size] uint8_t * cid, uint32_t cid_size + ); + + sgx_status_t ocall_write_ipfs( + [in, size = state_size] uint8_t * enc_state, uint32_t state_size, + [out, size = cid_size] uint8_t * cid, uint32_t cid_size + ); + + sgx_status_t ocall_worker_request( + [in, size = req_size] uint8_t * request, uint32_t req_size, + [out, size = resp_size] uint8_t * response, uint32_t resp_size + ); + + sgx_status_t ocall_update_metric( + [in, size = metric_size] uint8_t * metric, uint32_t metric_size + ); + + sgx_status_t ocall_propose_sidechain_blocks( + [in, size = signed_blocks_size] uint8_t * signed_blocks, uint32_t signed_blocks_size + ); + + sgx_status_t ocall_store_sidechain_blocks( + [in, size = signed_blocks_size] uint8_t * signed_blocks, uint32_t signed_blocks_size + ); + + sgx_status_t ocall_fetch_sidechain_blocks_from_peer( + [in, size = last_imported_block_hash_size] uint8_t * last_imported_block_hash, uint32_t last_imported_block_hash_size, + [in, size = maybe_until_block_hash_size] uint8_t * maybe_until_block_hash, uint32_t maybe_until_block_hash_size, + [in, size = shard_identifier_size] uint8_t * shard_identifier, uint32_t shard_identifier_size, + [out, size = sidechain_blocks_size] uint8_t * sidechain_blocks, uint32_t sidechain_blocks_size + ); + + sgx_status_t ocall_send_to_parentchain( + [in, size = extrinsics_size] uint8_t * extrinsics, uint32_t extrinsics_size + ); + }; +}; diff --git a/tee-worker/enclave-runtime/Enclave.lds b/tee-worker/enclave-runtime/Enclave.lds new file mode 100644 index 0000000000..e3d9d0ee0d --- /dev/null +++ b/tee-worker/enclave-runtime/Enclave.lds @@ -0,0 +1,9 @@ +enclave.so +{ + global: + g_global_data_sim; + g_global_data; + enclave_entry; + local: + *; +}; diff --git a/tee-worker/enclave-runtime/Enclave_private.pem b/tee-worker/enclave-runtime/Enclave_private.pem new file mode 100644 index 0000000000..529d07be35 --- /dev/null +++ b/tee-worker/enclave-runtime/Enclave_private.pem @@ -0,0 +1,39 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIG4gIBAAKCAYEAroOogvsj/fZDZY8XFdkl6dJmky0lRvnWMmpeH41Bla6U1qLZ +AmZuyIF+mQC/cgojIsrBMzBxb1kKqzATF4+XwPwgKz7fmiddmHyYz2WDJfAjIveJ +ZjdMjM4+EytGlkkJ52T8V8ds0/L2qKexJ+NBLxkeQLfV8n1mIk7zX7jguwbCG1Pr +nEMdJ3Sew20vnje+RsngAzdPChoJpVsWi/K7cettX/tbnre1DL02GXc5qJoQYk7b +3zkmhz31TgFrd9VVtmUGyFXAysuSAb3EN+5VnHGr0xKkeg8utErea2FNtNIgua8H +ONfm9Eiyaav1SVKzPHlyqLtcdxH3I8Wg7yqMsaprZ1n5A1v/levxnL8+It02KseD +5HqV4rf/cImSlCt3lpRg8U5E1pyFQ2IVEC/XTDMiI3c+AR+w2jSRB3Bwn9zJtFlW +KHG3m1xGI4ck+Lci1JvWWLXQagQSPtZTsubxTQNx1gsgZhgv1JHVZMdbVlAbbRMC +1nSuJNl7KPAS/VfzAgEDAoIBgHRXxaynbVP5gkO0ug6Qw/E27wzIw4SmjsxG6Wpe +K7kfDeRskKxESdsA/xCrKkwGwhcx1iIgS5+Qscd1Yg+1D9X9asd/P7waPmWoZd+Z +AhlKwhdPsO7PiF3e1AzHhGQwsUTt/Y/aSI1MpHBvy2/s1h9mFCslOUxTmWw0oj/Q +ldIEgWeNR72CE2+jFIJIyml6ftnb6qzPiga8Bm48ubKh0kvySOqnkmnPzgh+JBD6 +JnBmtZbfPT97bwTT+N6rnPqOOApvfHPf15kWI8yDbprG1l4OCUaIUH1AszxLd826 +5IPM+8gINLRDP1MA6azECPjTyHXhtnSIBZCyWSVkc05vYmNXYUNiXWMajcxW9M02 +wKzFELO8NCEAkaTPxwo4SCyIjUxiK1LbQ9h8PSy4c1+gGP4LAMR8xqP4QKg6zdu9 +osUGG/xRe/uufgTBFkcjqBHtK5L5VI0jeNIUAgW/6iNbYXjBMJ0GfauLs+g1VsOm +WfdgXzsb9DYdMa0OXXHypmV4GwKBwQDUwQj8RKJ6c8cT4vcWCoJvJF00+RFL+P3i +Gx2DLERxRrDa8AVGfqaCjsR+3vLgG8V/py+z+dxZYSqeB80Qeo6PDITcRKoeAYh9 +xlT3LJOS+k1cJcEmlbbO2IjLkTmzSwa80fWexKu8/Xv6vv15gpqYl1ngYoqJM3pd +vzmTIOi7MKSZ0WmEQavrZj8zK4endE3v0eAEeQ55j1GImbypSf7Idh7wOXtjZ7WD +Dg6yWDrri+AP/L3gClMj8wsAxMV4ZR8CgcEA0fzDHkFa6raVOxWnObmRoDhAtE0a +cjUj976NM5yyfdf2MrKy4/RhdTiPZ6b08/lBC/+xRfV3xKVGzacm6QjqjZrUpgHC +0LKiZaMtccCJjLtPwQd0jGQEnKfMFaPsnhOc5y8qVkCzVOSthY5qhz0XNotHHFmJ +gffVgB0iqrMTvSL7IA2yqqpOqNRlhaYhNl8TiFP3gIeMtVa9rZy31JPgT2uJ+kfo +gV7sdTPEjPWZd7OshGxWpT6QfVDj/T9T7L6tAoHBAI3WBf2DFvxNL2KXT2QHAZ9t +k3imC4f7U+wSE6zILaDZyzygA4RUbwG0gv8/TJVn2P/Eynf76DuWHGlaiLWnCbSz +Az2DHBQBBaku409zDQym3j1ugMRjzzSQWzJg0SIyBH3hTmnYcn3+Uqcp/lEBvGW6 +O+rsXFt3pukqJmIV8HzLGGaLm62BHUeZf3dyWm+i3p/hQAL7Xvu04QW70xuGqdr5 +afV7p5eaeQIJXyGQJ0eylV/90+qxjMKiB1XYg6WYvwKBwQCL/ddpgOdHJGN8uRom +e7Zq0Csi3hGheMKlKbN3vcxT5U7MdyHtTZZOJbTvxKNNUNYH/8uD+PqDGNneb29G +BfGzvI3EASyLIcGZF3OhKwZd0jUrWk2y7Vhob91jwp2+t73vdMbkKyI4mHOuXvGv +fg95si9oO7EBT+Oqvhccd2J+F1IVXncccYnF4u5ZGWt5lLewN/pVr7MjjykeaHqN +t+rfnQam2psA6fL4zS2zTmZPzR2tnY8Y1GBTi0Ko1OKd1HMCgcAb5cB/7/AQlhP9 +yQa04PLH9ygQkKKptZp7dy5WcWRx0K/hAHRoi2aw1wZqfm7VBNu2SLcs90kCCCxp +6C5sfJi6b8NpNbIPC+sc9wsFr7pGo9SFzQ78UlcWYK2Gu2FxlMjonhka5hvo4zvg +WxlpXKEkaFt3gLd92m/dMqBrHfafH7VwOJY2zT3WIpjwuk0ZzmRg5p0pG/svVQEH +NZmwRwlopysbR69B/n1nefJ84UO50fLh5s5Zr3gBRwbWNZyzhXk= +-----END RSA PRIVATE KEY----- diff --git a/tee-worker/enclave-runtime/Makefile b/tee-worker/enclave-runtime/Makefile new file mode 100644 index 0000000000..bed0a03ca2 --- /dev/null +++ b/tee-worker/enclave-runtime/Makefile @@ -0,0 +1,63 @@ +# Copyright (C) 2017-2018 Baidu, Inc. All Rights Reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in +# the documentation and/or other materials provided with the +# distribution. +# * Neither the name of Baidu, Inc., nor the names of its +# contributors may be used to endorse or promote products derived +# from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +######## Worker Feature Settings ######## +# Set sidechain as default feature mode +WORKER_MODE ?= sidechain + +Rust_Enclave_Name := libenclave.a +Rust_Enclave_Files := $(wildcard src/*.rs) $(wildcard ../stf/src/*.rs) +Rust_Target_Path := $(CURDIR)/../../../xargo +RUSTFLAGS :="-C target-feature=+avx2" + +ifeq ($(SGX_DEBUG), 1) + OUTPUT_PATH := release + CARGO_TARGET := --release +else + OUTPUT_PATH := release + CARGO_TARGET := --release +endif + +ifeq ($(SGX_PRODUCTION), 1) + ENCLAVE_FEATURES = --features=production,$(WORKER_MODE),$(ADDITIONAL_FEATURES) +else + ENCLAVE_FEATURES = --features=test,$(WORKER_MODE),$(ADDITIONAL_FEATURES) +endif + +.PHONY: all + +all: $(Rust_Enclave_Name) + +$(Rust_Enclave_Name): $(Rust_Enclave_Files) +ifeq ($(XARGO_SGX), 1) + RUST_TARGET_PATH=$(Rust_Target_Path) xargo build --target x86_64-unknown-linux-sgx $(CARGO_TARGET) + cp ./target/x86_64-unknown-linux-sgx/$(OUTPUT_PATH)/libenclave_runtime.a ../lib/libenclave.a +else + RUSTFLAGS=$(RUSTFLAGS) cargo build $(CARGO_TARGET) $(ENCLAVE_FEATURES) + cp ./target/$(OUTPUT_PATH)/libenclave_runtime.a ../lib/libenclave.a +endif diff --git a/tee-worker/enclave-runtime/Xargo.toml b/tee-worker/enclave-runtime/Xargo.toml new file mode 100644 index 0000000000..57ad5b829a --- /dev/null +++ b/tee-worker/enclave-runtime/Xargo.toml @@ -0,0 +1,20 @@ +[dependencies] +alloc = {} +panic_unwind = {} +panic_abort = {} + +[dependencies.std] +path = "../../../xargo/sgx_tstd" +stage = 1 + +[dependencies.sgx_rand] +path = "../../../xargo/sgx_rand" +stage = 2 + +[dependencies.sgx_serialize] +path = "../../../xargo/sgx_serialize" +stage = 2 + +[dependencies.sgx_tunittest] +path = "../../../xargo/sgx_tunittest" +stage = 2 \ No newline at end of file diff --git a/tee-worker/enclave-runtime/rust-toolchain.toml b/tee-worker/enclave-runtime/rust-toolchain.toml new file mode 100644 index 0000000000..23ed88e6c8 --- /dev/null +++ b/tee-worker/enclave-runtime/rust-toolchain.toml @@ -0,0 +1,4 @@ +[toolchain] +channel = "nightly-2022-10-22" +targets = ["wasm32-unknown-unknown"] +profile = "default" # include rustfmt, clippy diff --git a/tee-worker/enclave-runtime/rustfmt.toml b/tee-worker/enclave-runtime/rustfmt.toml new file mode 100644 index 0000000000..104b9aa998 --- /dev/null +++ b/tee-worker/enclave-runtime/rustfmt.toml @@ -0,0 +1,18 @@ +# Basic +hard_tabs = true +max_width = 100 +use_small_heuristics = "Max" +# Imports +imports_granularity = "Crate" +reorder_imports = true +# Consistency +newline_style = "Unix" +# Misc +chain_width = 80 +spaces_around_ranges = false +match_arm_leading_pipes = "Preserve" +match_arm_blocks = false +match_block_trailing_comma = true +trailing_comma = "Vertical" +trailing_semicolon = false +use_field_init_shorthand = true \ No newline at end of file diff --git a/tee-worker/enclave-runtime/src/attestation.rs b/tee-worker/enclave-runtime/src/attestation.rs new file mode 100644 index 0000000000..93e4117f3b --- /dev/null +++ b/tee-worker/enclave-runtime/src/attestation.rs @@ -0,0 +1,162 @@ +// Copyright 2022 Integritee AG and Supercomputing Systems AG +// Copyright (C) 2017-2019 Baidu, Inc. All Rights Reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in +// the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Baidu, Inc., nor the names of its +// contributors may be used to endorse or promote products derived +// from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +use crate::{ + initialization::global_components::GLOBAL_ATTESTATION_HANDLER_COMPONENT, + utils::{ + get_extrinsic_factory_from_solo_or_parachain, + get_node_metadata_repository_from_solo_or_parachain, + }, + Error as EnclaveError, Result as EnclaveResult, +}; +use codec::{Decode, Encode}; +use itp_attestation_handler::AttestationHandler; +use itp_component_container::ComponentGetter; +use itp_extrinsics_factory::CreateExtrinsics; +use itp_node_api::metadata::{ + pallet_teerex::TeerexCallIndexes, + provider::{AccessNodeMetadata, Error as MetadataProviderError}, +}; +use itp_settings::worker::MR_ENCLAVE_SIZE; +use itp_types::OpaqueCall; +use itp_utils::write_slice_and_whitespace_pad; +use log::*; +use sgx_types::*; +use sp_runtime::OpaqueExtrinsic; +use std::{prelude::v1::*, slice, vec::Vec}; + +#[no_mangle] +pub unsafe extern "C" fn get_mrenclave(mrenclave: *mut u8, mrenclave_size: usize) -> sgx_status_t { + if mrenclave.is_null() || mrenclave_size < MR_ENCLAVE_SIZE { + return sgx_status_t::SGX_ERROR_INVALID_PARAMETER + } + let attestation_handler = match GLOBAL_ATTESTATION_HANDLER_COMPONENT.get() { + Ok(r) => r, + Err(e) => { + error!("Component get failure: {:?}", e); + return sgx_status_t::SGX_ERROR_UNEXPECTED + }, + }; + match attestation_handler.get_mrenclave() { + Ok(mrenclave_value) => { + let mrenclave_slice = slice::from_raw_parts_mut(mrenclave, mrenclave_size); + if let Err(e) = + write_slice_and_whitespace_pad(mrenclave_slice, mrenclave_value.to_vec()) + { + error!("Failed to transfer mrenclave to o-call buffer: {:?}", e); + return sgx_status_t::SGX_ERROR_UNEXPECTED + } + sgx_status_t::SGX_SUCCESS + }, + Err(e) => e.into(), + } +} + +pub fn create_ra_report_and_signature( + sign_type: sgx_quote_sign_type_t, + skip_ra: bool, +) -> EnclaveResult<(Vec, Vec)> { + let attestation_handler = match GLOBAL_ATTESTATION_HANDLER_COMPONENT.get() { + Ok(r) => r, + Err(e) => { + error!("Component get failure: {:?}", e); + return Err(e.into()) + }, + }; + + match attestation_handler.create_ra_report_and_signature(sign_type, skip_ra) { + Ok(r) => Ok(r), + Err(e) => { + error!("create_ra_report_and_signature failure: {:?}", e); + Err(e.into()) + }, + } +} + +#[no_mangle] +pub unsafe extern "C" fn perform_ra( + w_url: *const u8, + w_url_size: u32, + unchecked_extrinsic: *mut u8, + unchecked_extrinsic_size: u32, + skip_ra: c_int, +) -> sgx_status_t { + if w_url.is_null() || unchecked_extrinsic.is_null() { + return sgx_status_t::SGX_ERROR_INVALID_PARAMETER + } + let mut url_slice = slice::from_raw_parts(w_url, w_url_size as usize); + let url = String::decode(&mut url_slice).expect("Could not decode url slice to a valid String"); + let extrinsic_slice = + slice::from_raw_parts_mut(unchecked_extrinsic, unchecked_extrinsic_size as usize); + + let extrinsic = match perform_ra_internal(url, skip_ra == 1) { + Ok(xt) => xt, + Err(e) => return e.into(), + }; + + if let Err(e) = write_slice_and_whitespace_pad(extrinsic_slice, extrinsic.encode()) { + return EnclaveError::Other(Box::new(e)).into() + }; + + sgx_status_t::SGX_SUCCESS +} + +fn perform_ra_internal(url: String, skip_ra: bool) -> EnclaveResult { + let attestation_handler = GLOBAL_ATTESTATION_HANDLER_COMPONENT.get()?; + let extrinsics_factory = get_extrinsic_factory_from_solo_or_parachain()?; + let node_metadata_repo = get_node_metadata_repository_from_solo_or_parachain()?; + + let cert_der = attestation_handler.perform_ra(skip_ra)?; + + info!(" [Enclave] Compose register enclave call"); + let call_ids = node_metadata_repo + .get_from_metadata(|m| m.register_enclave_call_indexes())? + .map_err(MetadataProviderError::MetadataError)?; + + let call = OpaqueCall::from_tuple(&(call_ids, cert_der, url)); + + let extrinsics = extrinsics_factory.create_extrinsics(&[call], None)?; + + Ok(extrinsics[0].clone()) +} + +#[no_mangle] +pub extern "C" fn dump_ra_to_disk() -> sgx_status_t { + let attestation_handler = match GLOBAL_ATTESTATION_HANDLER_COMPONENT.get() { + Ok(r) => r, + Err(e) => { + error!("Component get failure: {:?}", e); + return sgx_status_t::SGX_ERROR_UNEXPECTED + }, + }; + match attestation_handler.dump_ra_to_disk() { + Ok(_) => sgx_status_t::SGX_SUCCESS, + Err(e) => e.into(), + } +} diff --git a/tee-worker/enclave-runtime/src/empty_impls.rs b/tee-worker/enclave-runtime/src/empty_impls.rs new file mode 100644 index 0000000000..88052d1f39 --- /dev/null +++ b/tee-worker/enclave-runtime/src/empty_impls.rs @@ -0,0 +1,37 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +/// Empty tests entry for production mode. +#[cfg(not(feature = "test"))] +#[no_mangle] +pub extern "C" fn test_main_entrance() -> sgx_types::size_t { + unreachable!("Tests are not available when compiled in production mode.") +} + +/// Empty Teeracle market data implementation. +#[cfg(not(feature = "teeracle"))] +#[no_mangle] +pub unsafe extern "C" fn update_market_data_xt( + _crypto_currency_ptr: *const u8, + _crypto_currency_size: u32, + _fiat_currency_ptr: *const u8, + _fiat_currency_size: u32, + _unchecked_extrinsic: *mut u8, + _unchecked_extrinsic_size: u32, +) -> sgx_types::sgx_status_t { + unreachable!("Cannot update market data, teeracle feature is not enabled.") +} diff --git a/tee-worker/enclave-runtime/src/error.rs b/tee-worker/enclave-runtime/src/error.rs new file mode 100644 index 0000000000..3f1d63b28c --- /dev/null +++ b/tee-worker/enclave-runtime/src/error.rs @@ -0,0 +1,68 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +use derive_more::{Display, From}; +use sgx_types::sgx_status_t; +use std::{boxed::Box, result::Result as StdResult, string::String}; + +pub type Result = StdResult; + +#[derive(Debug, Display, From)] +pub enum Error { + TopPoolAuthor(itp_top_pool_author::error::Error), + Codec(codec::Error), + ComponentContainer(itp_component_container::error::Error), + Crypto(itp_sgx_crypto::Error), + ChainStorage(itp_ocall_api::Error), + ExtrinsicsFactory(itp_extrinsics_factory::error::Error), + StfTaskReceiver(lc_stf_task_receiver::Error), + IO(std::io::Error), + LightClient(itc_parentchain::light_client::error::Error), + NodeMetadataProvider(itp_node_api::metadata::provider::Error), + Sgx(sgx_status_t), + Consensus(its_sidechain::consensus_common::Error), + Stf(String), + StfStateHandler(itp_stf_state_handler::error::Error), + StfExecution(itp_stf_executor::error::Error), + ParentchainBlockImportDispatch(itc_parentchain::block_import_dispatcher::error::Error), + ExpectedTriggeredImportDispatcher, + CouldNotDispatchBlockImport, + NoParentchainAssigned, + PrimitivesAccess(itp_primitives_cache::error::Error), + MutexAccess, + Attestation(itp_attestation_handler::error::Error), + Other(Box), +} + +impl From for sgx_status_t { + /// return sgx_status for top level enclave functions + fn from(error: Error) -> sgx_status_t { + match error { + Error::Sgx(status) => status, + _ => { + log::error!("Returning error {:?} as sgx unexpected.", error); + sgx_status_t::SGX_ERROR_UNEXPECTED + }, + } + } +} + +impl From for StdResult { + fn from(error: Error) -> StdResult { + Err(error) + } +} diff --git a/tee-worker/enclave-runtime/src/initialization/global_components.rs b/tee-worker/enclave-runtime/src/initialization/global_components.rs new file mode 100644 index 0000000000..1f1f2ca327 --- /dev/null +++ b/tee-worker/enclave-runtime/src/initialization/global_components.rs @@ -0,0 +1,283 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +//! Defines all concrete types and global components of the enclave. +//! +//! This allows the crates themselves to stay as generic as possible +//! and ensures that the global instances are initialized once. + +use crate::{ + initialization::parentchain::{ + parachain::FullParachainHandler, solochain::FullSolochainHandler, + }, + ocall::OcallApi, + rpc::rpc_response_channel::RpcResponseChannel, + tls_ra::seal_handler::SealHandler, +}; +use ita_sgx_runtime::Runtime; +use ita_stf::{Getter, Hash, State as StfState, Stf, TrustedCallSigned}; +use itc_direct_rpc_server::{ + rpc_connection_registry::ConnectionRegistry, rpc_responder::RpcResponder, + rpc_watch_extractor::RpcWatchExtractor, rpc_ws_handler::RpcWsHandler, +}; +use itc_parentchain::{ + block_import_dispatcher::{ + immediate_dispatcher::ImmediateDispatcher, triggered_dispatcher::TriggeredDispatcher, + BlockImportDispatcher, + }, + block_importer::ParentchainBlockImporter, + indirect_calls_executor::IndirectCallsExecutor, + light_client::{ + concurrent_access::ValidatorAccessor, io::LightClientStateSeal, + light_validation::LightValidation, light_validation_state::LightValidationState, + }, +}; +use itc_tls_websocket_server::{ + config_provider::FromFileConfigProvider, ws_server::TungsteniteWsServer, ConnectionToken, +}; +use itp_attestation_handler::IasAttestationHandler; +use itp_block_import_queue::BlockImportQueue; +use itp_component_container::ComponentContainer; +use itp_extrinsics_factory::ExtrinsicsFactory; +use itp_node_api::metadata::{provider::NodeMetadataRepository, NodeMetadata}; +use itp_nonce_cache::NonceCache; +use itp_sgx_crypto::{key_repository::KeyRepository, Aes, AesSeal, Rsa3072Seal}; +use itp_sgx_externalities::SgxExternalities; +use itp_stf_executor::{ + enclave_signer::StfEnclaveSigner, executor::StfExecutor, getter_executor::GetterExecutor, + state_getter::StfStateGetter, +}; +use itp_stf_state_handler::{ + file_io::sgx::SgxStateFileIo, state_initializer::StateInitializer, + state_snapshot_repository::StateSnapshotRepository, StateHandler, +}; +use itp_stf_state_observer::state_observer::StateObserver; +use itp_top_pool::basic_pool::BasicPool; +use itp_top_pool_author::{ + api::SidechainApi, + author::{Author, AuthorTopFilter}, +}; +use itp_types::{Block as ParentchainBlock, SignedBlock as SignedParentchainBlock}; +use its_primitives::{ + traits::{Block as SidechainBlockTrait, SignedBlock as SignedSidechainBlockTrait}, + types::block::SignedBlock as SignedSidechainBlock, +}; +use its_sidechain::{ + aura::block_importer::BlockImporter as SidechainBlockImporter, + block_composer::BlockComposer, + consensus_common::{BlockImportConfirmationHandler, BlockImportQueueWorker, PeerBlockSync}, + state::SidechainDB, +}; +use sgx_crypto_helper::rsa3072::Rsa3072KeyPair; +use sp_core::ed25519::Pair; + +pub type EnclaveGetter = Getter; +pub type EnclaveTrustedCallSigned = TrustedCallSigned; +pub type EnclaveStf = Stf; +pub type EnclaveStateKeyRepository = KeyRepository; +pub type EnclaveShieldingKeyRepository = KeyRepository; +pub type EnclaveStateFileIo = SgxStateFileIo; +pub type EnclaveStateSnapshotRepository = StateSnapshotRepository; +pub type EnclaveStateObserver = StateObserver; +pub type EnclaveStateInitializer = + StateInitializer; +pub type EnclaveStateHandler = + StateHandler; +pub type EnclaveGetterExecutor = GetterExecutor>; +pub type EnclaveOCallApi = OcallApi; +pub type EnclaveNodeMetadataRepository = NodeMetadataRepository; +pub type EnclaveStfExecutor = + StfExecutor; +pub type EnclaveStfEnclaveSigner = StfEnclaveSigner< + EnclaveOCallApi, + EnclaveStateObserver, + EnclaveShieldingKeyRepository, + EnclaveStf, + EnclaveTopPoolAuthor, +>; +pub type EnclaveAttestationHandler = IasAttestationHandler; + +pub type EnclaveRpcConnectionRegistry = ConnectionRegistry; +pub type EnclaveRpcWsHandler = + RpcWsHandler, EnclaveRpcConnectionRegistry, Hash>; +pub type EnclaveWebSocketServer = TungsteniteWsServer; +pub type EnclaveRpcResponder = RpcResponder; +pub type EnclaveSidechainApi = SidechainApi; + +// Parentchain types +pub type EnclaveExtrinsicsFactory = + ExtrinsicsFactory; +pub type EnclaveIndirectCallsExecutor = IndirectCallsExecutor< + EnclaveShieldingKeyRepository, + EnclaveStfEnclaveSigner, + EnclaveTopPoolAuthor, + EnclaveNodeMetadataRepository, +>; +pub type EnclaveValidatorAccessor = ValidatorAccessor< + LightValidation, + ParentchainBlock, + LightClientStateSeal>, +>; +pub type EnclaveParentchainBlockImporter = ParentchainBlockImporter< + ParentchainBlock, + EnclaveValidatorAccessor, + EnclaveStfExecutor, + EnclaveExtrinsicsFactory, + EnclaveIndirectCallsExecutor, +>; +pub type EnclaveParentchainBlockImportQueue = BlockImportQueue; +pub type EnclaveTriggeredParentchainBlockImportDispatcher = + TriggeredDispatcher; + +pub type EnclaveImmediateParentchainBlockImportDispatcher = + ImmediateDispatcher; + +pub type EnclaveParentchainBlockImportDispatcher = BlockImportDispatcher< + EnclaveTriggeredParentchainBlockImportDispatcher, + EnclaveImmediateParentchainBlockImportDispatcher, +>; + +/// Sidechain types +pub type EnclaveSidechainState = + SidechainDB<::Block, SgxExternalities>; +pub type EnclaveTopPool = BasicPool; + +pub type EnclaveTopPoolAuthor = Author< + EnclaveTopPool, + AuthorTopFilter, + EnclaveStateHandler, + EnclaveShieldingKeyRepository, + EnclaveOCallApi, +>; +pub type EnclaveSidechainBlockComposer = + BlockComposer; +pub type EnclaveSidechainBlockImporter = SidechainBlockImporter< + Pair, + ParentchainBlock, + SignedSidechainBlock, + EnclaveOCallApi, + EnclaveSidechainState, + EnclaveStateHandler, + EnclaveStateKeyRepository, + EnclaveTopPoolAuthor, + EnclaveTriggeredParentchainBlockImportDispatcher, +>; +pub type EnclaveSidechainBlockImportQueue = BlockImportQueue; +pub type EnclaveBlockImportConfirmationHandler = BlockImportConfirmationHandler< + ParentchainBlock, + <::Block as SidechainBlockTrait>::HeaderType, + EnclaveNodeMetadataRepository, + EnclaveExtrinsicsFactory, + EnclaveValidatorAccessor, +>; +pub type EnclaveSidechainBlockSyncer = PeerBlockSync< + ParentchainBlock, + SignedSidechainBlock, + EnclaveSidechainBlockImporter, + EnclaveOCallApi, + EnclaveBlockImportConfirmationHandler, +>; +pub type EnclaveSidechainBlockImportQueueWorker = BlockImportQueueWorker< + ParentchainBlock, + SignedSidechainBlock, + EnclaveSidechainBlockImportQueue, + EnclaveSidechainBlockSyncer, +>; +pub type EnclaveSealHandler = + SealHandler; +pub type EnclaveOffchainWorkerExecutor = itc_offchain_worker_executor::executor::Executor< + ParentchainBlock, + EnclaveTopPoolAuthor, + EnclaveStfExecutor, + EnclaveStateHandler, + EnclaveValidatorAccessor, + EnclaveExtrinsicsFactory, + EnclaveStf, +>; + +/// Base component instances +///------------------------------------------------------------------------------------------------- + +/// State key repository +pub static GLOBAL_STATE_KEY_REPOSITORY_COMPONENT: ComponentContainer = + ComponentContainer::new("State key repository"); + +/// Shielding key repository +pub static GLOBAL_SHIELDING_KEY_REPOSITORY_COMPONENT: ComponentContainer< + EnclaveShieldingKeyRepository, +> = ComponentContainer::new("Shielding key repository"); + +/// O-Call API +pub static GLOBAL_OCALL_API_COMPONENT: ComponentContainer = + ComponentContainer::new("O-call API"); + +/// Trusted Web-socket server +pub static GLOBAL_WEB_SOCKET_SERVER_COMPONENT: ComponentContainer = + ComponentContainer::new("Web-socket server"); + +/// State handler. +pub static GLOBAL_STATE_HANDLER_COMPONENT: ComponentContainer = + ComponentContainer::new("state handler"); + +/// State observer. +pub static GLOBAL_STATE_OBSERVER_COMPONENT: ComponentContainer = + ComponentContainer::new("state observer"); + +/// TOP pool author. +pub static GLOBAL_TOP_POOL_AUTHOR_COMPONENT: ComponentContainer = + ComponentContainer::new("top_pool_author"); + +/// attestation handler +pub static GLOBAL_ATTESTATION_HANDLER_COMPONENT: ComponentContainer = + ComponentContainer::new("Attestation handler"); + +/// Parentchain component instances +///------------------------------------------------------------------------------------------------- + +/// Solochain Handler. +pub static GLOBAL_FULL_SOLOCHAIN_HANDLER_COMPONENT: ComponentContainer = + ComponentContainer::new("full solochain handler"); + +pub static GLOBAL_FULL_PARACHAIN_HANDLER_COMPONENT: ComponentContainer = + ComponentContainer::new("full parachain handler"); + +/// Sidechain component instances +///------------------------------------------------------------------------------------------------- + +/// Enclave RPC WS handler. +pub static GLOBAL_RPC_WS_HANDLER_COMPONENT: ComponentContainer = + ComponentContainer::new("rpc_ws_handler"); + +/// Sidechain import queue. +pub static GLOBAL_SIDECHAIN_IMPORT_QUEUE_COMPONENT: ComponentContainer< + EnclaveSidechainBlockImportQueue, +> = ComponentContainer::new("sidechain_import_queue"); + +/// Sidechain import queue worker - processes the import queue. +pub static GLOBAL_SIDECHAIN_IMPORT_QUEUE_WORKER_COMPONENT: ComponentContainer< + EnclaveSidechainBlockImportQueueWorker, +> = ComponentContainer::new("sidechain_import_queue_worker"); + +/// Sidechain block composer. +pub static GLOBAL_SIDECHAIN_BLOCK_COMPOSER_COMPONENT: ComponentContainer< + EnclaveSidechainBlockComposer, +> = ComponentContainer::new("sidechain_block_composer"); + +/// Sidechain block syncer. +pub static GLOBAL_SIDECHAIN_BLOCK_SYNCER_COMPONENT: ComponentContainer< + EnclaveSidechainBlockSyncer, +> = ComponentContainer::new("sidechain_block_syncer"); diff --git a/tee-worker/enclave-runtime/src/initialization/initialization.rs b/tee-worker/enclave-runtime/src/initialization/initialization.rs new file mode 100644 index 0000000000..29cac97010 --- /dev/null +++ b/tee-worker/enclave-runtime/src/initialization/initialization.rs @@ -0,0 +1,313 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ +use crate::{ + error::{Error, Result as EnclaveResult}, + initialization::global_components::{ + EnclaveBlockImportConfirmationHandler, EnclaveGetterExecutor, EnclaveOCallApi, + EnclaveOffchainWorkerExecutor, EnclaveRpcConnectionRegistry, EnclaveRpcResponder, + EnclaveShieldingKeyRepository, EnclaveSidechainApi, EnclaveSidechainBlockImportQueue, + EnclaveSidechainBlockImportQueueWorker, EnclaveSidechainBlockImporter, + EnclaveSidechainBlockSyncer, EnclaveStateFileIo, EnclaveStateHandler, + EnclaveStateInitializer, EnclaveStateKeyRepository, EnclaveStateObserver, + EnclaveStateSnapshotRepository, EnclaveStfEnclaveSigner, EnclaveStfExecutor, + EnclaveTopPool, EnclaveTopPoolAuthor, EnclaveValidatorAccessor, + GLOBAL_ATTESTATION_HANDLER_COMPONENT, GLOBAL_EXTRINSICS_FACTORY_COMPONENT, + GLOBAL_IMMEDIATE_PARENTCHAIN_IMPORT_DISPATCHER_COMPONENT, + GLOBAL_NODE_METADATA_REPOSITORY_COMPONENT, GLOBAL_OCALL_API_COMPONENT, + GLOBAL_PARENTCHAIN_BLOCK_VALIDATOR_ACCESS_COMPONENT, GLOBAL_RPC_WS_HANDLER_COMPONENT, + GLOBAL_SHIELDING_KEY_REPOSITORY_COMPONENT, GLOBAL_SIDECHAIN_BLOCK_COMPOSER_COMPONENT, + GLOBAL_SIDECHAIN_BLOCK_SYNCER_COMPONENT, GLOBAL_SIDECHAIN_IMPORT_QUEUE_COMPONENT, + GLOBAL_SIDECHAIN_IMPORT_QUEUE_WORKER_COMPONENT, GLOBAL_STATE_HANDLER_COMPONENT, + GLOBAL_STATE_KEY_REPOSITORY_COMPONENT, GLOBAL_STATE_OBSERVER_COMPONENT, + GLOBAL_STF_EXECUTOR_COMPONENT, GLOBAL_TOP_POOL_AUTHOR_COMPONENT, + GLOBAL_TRIGGERED_PARENTCHAIN_IMPORT_DISPATCHER_COMPONENT, + GLOBAL_WEB_SOCKET_SERVER_COMPONENT, + }, + ocall::OcallApi, + rpc::{rpc_response_channel::RpcResponseChannel, worker_api_direct::public_api_rpc_handler}, + utils::{ + get_extrinsic_factory_from_solo_or_parachain, + get_node_metadata_repository_from_solo_or_parachain, + get_validator_accessor_from_solo_or_parachain, + }, + Hash, +}; +use base58::ToBase58; +use codec::Encode; +use itc_direct_rpc_server::{ + create_determine_watch, rpc_connection_registry::ConnectionRegistry, + rpc_ws_handler::RpcWsHandler, +}; +use itc_parentchain::{ + block_import_dispatcher::{ + immediate_dispatcher::ImmediateDispatcher, triggered_dispatcher::TriggeredDispatcher, + }, + block_importer::ParentchainBlockImporter, + indirect_calls_executor::IndirectCallsExecutor, + light_client::{ + concurrent_access::ValidatorAccess, light_client_init_params::LightClientInitParams, + LightClientState, + }, +}; +use itc_tls_websocket_server::{ + certificate_generation::ed25519_self_signed_certificate, create_ws_server, ConnectionToken, + WebSocketServer, +}; +use itp_attestation_handler::IasAttestationHandler; +use itp_block_import_queue::BlockImportQueue; +use itp_component_container::{ComponentGetter, ComponentInitializer}; +use itp_extrinsics_factory::ExtrinsicsFactory; +use itp_node_api::metadata::provider::NodeMetadataRepository; +use itp_nonce_cache::GLOBAL_NONCE_CACHE; +use itp_primitives_cache::GLOBAL_PRIMITIVES_CACHE; +use itp_settings::{ + files::STATE_SNAPSHOTS_CACHE_SIZE, + worker_mode::{ProvideWorkerMode, WorkerMode}, +}; +use itp_sgx_crypto::{aes, ed25519, rsa3072, AesSeal, Ed25519Seal, Rsa3072Seal}; +use itp_sgx_io::StaticSealedIO; +use itp_stf_state_handler::{ + handle_state::HandleState, query_shard_state::QueryShardState, + state_snapshot_repository::VersionedStateAccess, + state_snapshot_repository_loader::StateSnapshotRepositoryLoader, StateHandler, +}; +use itp_top_pool::pool::Options as PoolOptions; +use itp_top_pool_author::author::AuthorTopFilter; +use itp_types::{Block, Header, ShardIdentifier, SignedBlock}; +use its_sidechain::block_composer::BlockComposer; +use log::*; +use sp_core::crypto::Pair; +use std::{collections::HashMap, string::String, sync::Arc}; + +pub(crate) fn init_enclave(mu_ra_url: String, untrusted_worker_url: String) -> EnclaveResult<()> { + // Initialize the logging environment in the enclave. + env_logger::init(); + + ed25519::create_sealed_if_absent().map_err(Error::Crypto)?; + let signer = Ed25519Seal::unseal_from_static_file().map_err(Error::Crypto)?; + info!("[Enclave initialized] Ed25519 prim raw : {:?}", signer.public().0); + + rsa3072::create_sealed_if_absent()?; + + let shielding_key = Rsa3072Seal::unseal_from_static_file()?; + + let shielding_key_repository = + Arc::new(EnclaveShieldingKeyRepository::new(shielding_key, Arc::new(Rsa3072Seal))); + GLOBAL_SHIELDING_KEY_REPOSITORY_COMPONENT.initialize(shielding_key_repository.clone()); + + // Create the aes key that is used for state encryption such that a key is always present in tests. + // It will be overwritten anyway if mutual remote attestation is performed with the primary worker. + aes::create_sealed_if_absent().map_err(Error::Crypto)?; + + let state_key = AesSeal::unseal_from_static_file()?; + let state_key_repository = + Arc::new(EnclaveStateKeyRepository::new(state_key, Arc::new(AesSeal))); + GLOBAL_STATE_KEY_REPOSITORY_COMPONENT.initialize(state_key_repository.clone()); + + let state_file_io = Arc::new(EnclaveStateFileIo::new(state_key_repository)); + let state_initializer = + Arc::new(EnclaveStateInitializer::new(shielding_key_repository.clone())); + let state_snapshot_repository_loader = StateSnapshotRepositoryLoader::< + EnclaveStateFileIo, + EnclaveStateInitializer, + >::new(state_file_io, state_initializer.clone()); + + let state_snapshot_repository = + state_snapshot_repository_loader.load_snapshot_repository(STATE_SNAPSHOTS_CACHE_SIZE)?; + let state_observer = initialize_state_observer(&state_snapshot_repository)?; + GLOBAL_STATE_OBSERVER_COMPONENT.initialize(state_observer.clone()); + + let state_handler = Arc::new(StateHandler::load_from_repository( + state_snapshot_repository, + state_observer.clone(), + state_initializer, + )?); + + GLOBAL_STATE_HANDLER_COMPONENT.initialize(state_handler.clone()); + + let ocall_api = Arc::new(OcallApi); + GLOBAL_OCALL_API_COMPONENT.initialize(ocall_api.clone()); + + // For debug purposes, list shards. no problem to panic if fails. + let shards = state_handler.list_shards().unwrap(); + debug!("found the following {} shards on disk:", shards.len()); + for s in shards { + debug!("{}", s.encode().to_base58()) + } + + itp_primitives_cache::set_primitives( + GLOBAL_PRIMITIVES_CACHE.as_ref(), + mu_ra_url, + untrusted_worker_url, + ) + .map_err(Error::PrimitivesAccess)?; + + let watch_extractor = Arc::new(create_determine_watch::()); + + let connection_registry = Arc::new(ConnectionRegistry::::new()); + + // We initialize components for the public RPC / direct invocation server here, so we can start the server + // before registering on the parentchain. If we started the RPC AFTER registering on the parentchain and + // initializing the light-client, there is a period of time where a peer might want to reach us, + // but the RPC server is not yet up and running, resulting in error messages or even in that + // validateer completely breaking (IO PipeError). + // Corresponding GH issues are #545 and #600. + + let top_pool_author = create_top_pool_author( + connection_registry.clone(), + state_handler, + ocall_api.clone(), + shielding_key_repository, + ); + GLOBAL_TOP_POOL_AUTHOR_COMPONENT.initialize(top_pool_author.clone()); + + let getter_executor = Arc::new(EnclaveGetterExecutor::new(state_observer)); + let io_handler = public_api_rpc_handler(top_pool_author, getter_executor); + let rpc_handler = Arc::new(RpcWsHandler::new(io_handler, watch_extractor, connection_registry)); + GLOBAL_RPC_WS_HANDLER_COMPONENT.initialize(rpc_handler); + + let sidechain_block_import_queue = Arc::new(EnclaveSidechainBlockImportQueue::default()); + GLOBAL_SIDECHAIN_IMPORT_QUEUE_COMPONENT.initialize(sidechain_block_import_queue); + + let attestation_handler = Arc::new(IasAttestationHandler::new(ocall_api)); + GLOBAL_ATTESTATION_HANDLER_COMPONENT.initialize(attestation_handler); + + Ok(()) +} + +fn initialize_state_observer( + snapshot_repository: &EnclaveStateSnapshotRepository, +) -> EnclaveResult> { + let shards = snapshot_repository.list_shards()?; + let mut states_map = HashMap::< + ShardIdentifier, + ::StateType, + >::new(); + for shard in shards.into_iter() { + let state = snapshot_repository.load_latest(&shard)?; + states_map.insert(shard, state); + } + Ok(Arc::new(EnclaveStateObserver::from_map(states_map))) +} + +pub(crate) fn init_enclave_sidechain_components() -> EnclaveResult<()> { + let state_handler = GLOBAL_STATE_HANDLER_COMPONENT.get()?; + let ocall_api = GLOBAL_OCALL_API_COMPONENT.get()?; + let top_pool_author = GLOBAL_TOP_POOL_AUTHOR_COMPONENT.get()?; + + let parentchain_block_import_dispatcher = + utils::get_triggered_dispatcher_from_solo_or_parachain()?; + + let state_key_repository = GLOBAL_STATE_KEY_REPOSITORY_COMPONENT.get()?; + + let signer = Ed25519Seal::unseal_from_static_file()?; + + let sidechain_block_importer = Arc::new(EnclaveSidechainBlockImporter::new( + state_handler, + state_key_repository.clone(), + top_pool_author, + parentchain_block_import_dispatcher, + ocall_api.clone(), + )); + + let sidechain_block_import_queue = GLOBAL_SIDECHAIN_IMPORT_QUEUE_COMPONENT.get()?; + let metadata_repository = get_node_metadata_repository_from_solo_or_parachain()?; + let extrinsics_factory = get_extrinsic_factory_from_solo_or_parachain()?; + let validator_accessor = get_validator_accessor_from_solo_or_parachain()?; + + let sidechain_block_import_confirmation_handler = + Arc::new(EnclaveBlockImportConfirmationHandler::new( + metadata_repository, + extrinsics_factory, + validator_accessor, + )); + + let sidechain_block_syncer = Arc::new(EnclaveSidechainBlockSyncer::new( + sidechain_block_importer, + ocall_api, + sidechain_block_import_confirmation_handler, + )); + GLOBAL_SIDECHAIN_BLOCK_SYNCER_COMPONENT.initialize(sidechain_block_syncer.clone()); + + let sidechain_block_import_queue_worker = + Arc::new(EnclaveSidechainBlockImportQueueWorker::new( + sidechain_block_import_queue, + sidechain_block_syncer, + )); + GLOBAL_SIDECHAIN_IMPORT_QUEUE_WORKER_COMPONENT.initialize(sidechain_block_import_queue_worker); + + let block_composer = Arc::new(BlockComposer::new(signer, state_key_repository)); + GLOBAL_SIDECHAIN_BLOCK_COMPOSER_COMPONENT.initialize(block_composer); + + Ok(()) +} + +pub(crate) fn init_direct_invocation_server(server_addr: String) -> EnclaveResult<()> { + let rpc_handler = GLOBAL_RPC_WS_HANDLER_COMPONENT.get()?; + let signing = Ed25519Seal::unseal_from_static_file()?; + + let cert = + ed25519_self_signed_certificate(signing, "Enclave").map_err(|e| Error::Other(e.into()))?; + + // Serialize certificate(s) and private key to PEM. + // PEM format is needed as a certificate chain can only be serialized into PEM. + let pem_serialized = cert.serialize_pem().map_err(|e| Error::Other(e.into()))?; + let private_key = cert.serialize_private_key_pem(); + + let web_socket_server = + create_ws_server(server_addr.as_str(), &private_key, &pem_serialized, rpc_handler); + + GLOBAL_WEB_SOCKET_SERVER_COMPONENT.initialize(web_socket_server.clone()); + + match web_socket_server.run() { + Ok(()) => {}, + Err(e) => { + error!("Web socket server encountered an unexpected error: {:?}", e) + }, + } + + Ok(()) +} + +pub(crate) fn init_shard(shard: ShardIdentifier) -> EnclaveResult<()> { + let state_handler = GLOBAL_STATE_HANDLER_COMPONENT.get()?; + let _ = state_handler.initialize_shard(shard)?; + Ok(()) +} + +/// Initialize the TOP pool author component. +pub fn create_top_pool_author( + connection_registry: Arc, + state_handler: Arc, + ocall_api: Arc, + shielding_key_repository: Arc, +) -> Arc { + let response_channel = Arc::new(RpcResponseChannel::default()); + let rpc_responder = Arc::new(EnclaveRpcResponder::new(connection_registry, response_channel)); + + let side_chain_api = Arc::new(EnclaveSidechainApi::new()); + let top_pool = + Arc::new(EnclaveTopPool::create(PoolOptions::default(), side_chain_api, rpc_responder)); + + Arc::new(EnclaveTopPoolAuthor::new( + top_pool, + AuthorTopFilter {}, + state_handler, + shielding_key_repository, + ocall_api, + )) +} diff --git a/tee-worker/enclave-runtime/src/initialization/mod.rs b/tee-worker/enclave-runtime/src/initialization/mod.rs new file mode 100644 index 0000000000..5c08fc087a --- /dev/null +++ b/tee-worker/enclave-runtime/src/initialization/mod.rs @@ -0,0 +1,294 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +pub mod global_components; +pub mod parentchain; + +use crate::{ + error::{Error, Result as EnclaveResult}, + initialization::global_components::{ + EnclaveBlockImportConfirmationHandler, EnclaveGetterExecutor, EnclaveOCallApi, + EnclaveRpcConnectionRegistry, EnclaveRpcResponder, EnclaveShieldingKeyRepository, + EnclaveSidechainApi, EnclaveSidechainBlockImportQueue, + EnclaveSidechainBlockImportQueueWorker, EnclaveSidechainBlockImporter, + EnclaveSidechainBlockSyncer, EnclaveStateFileIo, EnclaveStateHandler, + EnclaveStateInitializer, EnclaveStateKeyRepository, EnclaveStateObserver, + EnclaveStateSnapshotRepository, EnclaveStfEnclaveSigner, EnclaveTopPool, + EnclaveTopPoolAuthor, GLOBAL_ATTESTATION_HANDLER_COMPONENT, GLOBAL_OCALL_API_COMPONENT, + GLOBAL_RPC_WS_HANDLER_COMPONENT, GLOBAL_SHIELDING_KEY_REPOSITORY_COMPONENT, + GLOBAL_SIDECHAIN_BLOCK_COMPOSER_COMPONENT, GLOBAL_SIDECHAIN_BLOCK_SYNCER_COMPONENT, + GLOBAL_SIDECHAIN_IMPORT_QUEUE_COMPONENT, GLOBAL_SIDECHAIN_IMPORT_QUEUE_WORKER_COMPONENT, + GLOBAL_STATE_HANDLER_COMPONENT, GLOBAL_STATE_KEY_REPOSITORY_COMPONENT, + GLOBAL_STATE_OBSERVER_COMPONENT, GLOBAL_TOP_POOL_AUTHOR_COMPONENT, + GLOBAL_WEB_SOCKET_SERVER_COMPONENT, + }, + ocall::OcallApi, + rpc::{rpc_response_channel::RpcResponseChannel, worker_api_direct::public_api_rpc_handler}, + utils::{ + get_extrinsic_factory_from_solo_or_parachain, + get_node_metadata_repository_from_solo_or_parachain, + get_triggered_dispatcher_from_solo_or_parachain, + get_validator_accessor_from_solo_or_parachain, + }, + Hash, +}; +use base58::ToBase58; +use codec::Encode; +use itc_direct_rpc_server::{ + create_determine_watch, rpc_connection_registry::ConnectionRegistry, + rpc_ws_handler::RpcWsHandler, +}; +use itc_tls_websocket_server::{ + certificate_generation::ed25519_self_signed_certificate, create_ws_server, ConnectionToken, + WebSocketServer, +}; +use itp_attestation_handler::IasAttestationHandler; +use itp_component_container::{ComponentGetter, ComponentInitializer}; +use itp_primitives_cache::GLOBAL_PRIMITIVES_CACHE; +use itp_settings::files::STATE_SNAPSHOTS_CACHE_SIZE; +use itp_sgx_crypto::{aes, ed25519, rsa3072, AesSeal, Ed25519Seal, Rsa3072Seal}; +use itp_sgx_io::StaticSealedIO; +use itp_stf_state_handler::{ + handle_state::HandleState, query_shard_state::QueryShardState, + state_snapshot_repository::VersionedStateAccess, + state_snapshot_repository_loader::StateSnapshotRepositoryLoader, StateHandler, +}; +use itp_top_pool::pool::Options as PoolOptions; +use itp_top_pool_author::author::AuthorTopFilter; +use itp_types::ShardIdentifier; +use its_sidechain::block_composer::BlockComposer; +use log::*; +use sp_core::crypto::Pair; +use std::{collections::HashMap, string::String, sync::Arc}; + +pub(crate) fn init_enclave(mu_ra_url: String, untrusted_worker_url: String) -> EnclaveResult<()> { + // Initialize the logging environment in the enclave. + env_logger::init(); + + ed25519::create_sealed_if_absent().map_err(Error::Crypto)?; + let signer = Ed25519Seal::unseal_from_static_file().map_err(Error::Crypto)?; + info!("[Enclave initialized] Ed25519 prim raw : {:?}", signer.public().0); + + rsa3072::create_sealed_if_absent()?; + + let shielding_key = Rsa3072Seal::unseal_from_static_file()?; + + let shielding_key_repository = + Arc::new(EnclaveShieldingKeyRepository::new(shielding_key, Arc::new(Rsa3072Seal))); + GLOBAL_SHIELDING_KEY_REPOSITORY_COMPONENT.initialize(shielding_key_repository.clone()); + + // Create the aes key that is used for state encryption such that a key is always present in tests. + // It will be overwritten anyway if mutual remote attestation is performed with the primary worker. + aes::create_sealed_if_absent().map_err(Error::Crypto)?; + + let state_key = AesSeal::unseal_from_static_file()?; + let state_key_repository = + Arc::new(EnclaveStateKeyRepository::new(state_key, Arc::new(AesSeal))); + GLOBAL_STATE_KEY_REPOSITORY_COMPONENT.initialize(state_key_repository.clone()); + + let state_file_io = Arc::new(EnclaveStateFileIo::new(state_key_repository)); + let state_initializer = + Arc::new(EnclaveStateInitializer::new(shielding_key_repository.clone())); + let state_snapshot_repository_loader = StateSnapshotRepositoryLoader::< + EnclaveStateFileIo, + EnclaveStateInitializer, + >::new(state_file_io, state_initializer.clone()); + + let state_snapshot_repository = + state_snapshot_repository_loader.load_snapshot_repository(STATE_SNAPSHOTS_CACHE_SIZE)?; + let state_observer = initialize_state_observer(&state_snapshot_repository)?; + GLOBAL_STATE_OBSERVER_COMPONENT.initialize(state_observer.clone()); + + let state_handler = Arc::new(StateHandler::load_from_repository( + state_snapshot_repository, + state_observer.clone(), + state_initializer, + )?); + + GLOBAL_STATE_HANDLER_COMPONENT.initialize(state_handler.clone()); + + let ocall_api = Arc::new(OcallApi); + GLOBAL_OCALL_API_COMPONENT.initialize(ocall_api.clone()); + + // For debug purposes, list shards. no problem to panic if fails. + let shards = state_handler.list_shards().unwrap(); + debug!("found the following {} shards on disk:", shards.len()); + for s in shards { + debug!("{}", s.encode().to_base58()) + } + + itp_primitives_cache::set_primitives( + GLOBAL_PRIMITIVES_CACHE.as_ref(), + mu_ra_url, + untrusted_worker_url, + ) + .map_err(Error::PrimitivesAccess)?; + + let watch_extractor = Arc::new(create_determine_watch::()); + + let connection_registry = Arc::new(ConnectionRegistry::::new()); + + // We initialize components for the public RPC / direct invocation server here, so we can start the server + // before registering on the parentchain. If we started the RPC AFTER registering on the parentchain and + // initializing the light-client, there is a period of time where a peer might want to reach us, + // but the RPC server is not yet up and running, resulting in error messages or even in that + // validateer completely breaking (IO PipeError). + // Corresponding GH issues are #545 and #600. + + let top_pool_author = create_top_pool_author( + connection_registry.clone(), + state_handler, + ocall_api.clone(), + shielding_key_repository, + ); + GLOBAL_TOP_POOL_AUTHOR_COMPONENT.initialize(top_pool_author.clone()); + + let getter_executor = Arc::new(EnclaveGetterExecutor::new(state_observer)); + let io_handler = public_api_rpc_handler(top_pool_author, getter_executor); + let rpc_handler = Arc::new(RpcWsHandler::new(io_handler, watch_extractor, connection_registry)); + GLOBAL_RPC_WS_HANDLER_COMPONENT.initialize(rpc_handler); + + let sidechain_block_import_queue = Arc::new(EnclaveSidechainBlockImportQueue::default()); + GLOBAL_SIDECHAIN_IMPORT_QUEUE_COMPONENT.initialize(sidechain_block_import_queue); + + let attestation_handler = Arc::new(IasAttestationHandler::new(ocall_api)); + GLOBAL_ATTESTATION_HANDLER_COMPONENT.initialize(attestation_handler); + + Ok(()) +} + +fn initialize_state_observer( + snapshot_repository: &EnclaveStateSnapshotRepository, +) -> EnclaveResult> { + let shards = snapshot_repository.list_shards()?; + let mut states_map = HashMap::< + ShardIdentifier, + ::StateType, + >::new(); + for shard in shards.into_iter() { + let state = snapshot_repository.load_latest(&shard)?; + states_map.insert(shard, state); + } + Ok(Arc::new(EnclaveStateObserver::from_map(states_map))) +} + +pub(crate) fn init_enclave_sidechain_components() -> EnclaveResult<()> { + let state_handler = GLOBAL_STATE_HANDLER_COMPONENT.get()?; + let ocall_api = GLOBAL_OCALL_API_COMPONENT.get()?; + let top_pool_author = GLOBAL_TOP_POOL_AUTHOR_COMPONENT.get()?; + + let parentchain_block_import_dispatcher = get_triggered_dispatcher_from_solo_or_parachain()?; + + let state_key_repository = GLOBAL_STATE_KEY_REPOSITORY_COMPONENT.get()?; + + let signer = Ed25519Seal::unseal_from_static_file()?; + + let sidechain_block_importer = Arc::new(EnclaveSidechainBlockImporter::new( + state_handler, + state_key_repository.clone(), + top_pool_author, + parentchain_block_import_dispatcher, + ocall_api.clone(), + )); + + let sidechain_block_import_queue = GLOBAL_SIDECHAIN_IMPORT_QUEUE_COMPONENT.get()?; + let metadata_repository = get_node_metadata_repository_from_solo_or_parachain()?; + let extrinsics_factory = get_extrinsic_factory_from_solo_or_parachain()?; + let validator_accessor = get_validator_accessor_from_solo_or_parachain()?; + + let sidechain_block_import_confirmation_handler = + Arc::new(EnclaveBlockImportConfirmationHandler::new( + metadata_repository, + extrinsics_factory, + validator_accessor, + )); + + let sidechain_block_syncer = Arc::new(EnclaveSidechainBlockSyncer::new( + sidechain_block_importer, + ocall_api, + sidechain_block_import_confirmation_handler, + )); + GLOBAL_SIDECHAIN_BLOCK_SYNCER_COMPONENT.initialize(sidechain_block_syncer.clone()); + + let sidechain_block_import_queue_worker = + Arc::new(EnclaveSidechainBlockImportQueueWorker::new( + sidechain_block_import_queue, + sidechain_block_syncer, + )); + GLOBAL_SIDECHAIN_IMPORT_QUEUE_WORKER_COMPONENT.initialize(sidechain_block_import_queue_worker); + + let block_composer = Arc::new(BlockComposer::new(signer, state_key_repository)); + GLOBAL_SIDECHAIN_BLOCK_COMPOSER_COMPONENT.initialize(block_composer); + + Ok(()) +} + +pub(crate) fn init_direct_invocation_server(server_addr: String) -> EnclaveResult<()> { + let rpc_handler = GLOBAL_RPC_WS_HANDLER_COMPONENT.get()?; + let signing = Ed25519Seal::unseal_from_static_file()?; + + let cert = + ed25519_self_signed_certificate(signing, "Enclave").map_err(|e| Error::Other(e.into()))?; + + // Serialize certificate(s) and private key to PEM. + // PEM format is needed as a certificate chain can only be serialized into PEM. + let pem_serialized = cert.serialize_pem().map_err(|e| Error::Other(e.into()))?; + let private_key = cert.serialize_private_key_pem(); + + let web_socket_server = + create_ws_server(server_addr.as_str(), &private_key, &pem_serialized, rpc_handler); + + GLOBAL_WEB_SOCKET_SERVER_COMPONENT.initialize(web_socket_server.clone()); + + match web_socket_server.run() { + Ok(()) => {}, + Err(e) => { + error!("Web socket server encountered an unexpected error: {:?}", e) + }, + } + + Ok(()) +} + +pub(crate) fn init_shard(shard: ShardIdentifier) -> EnclaveResult<()> { + let state_handler = GLOBAL_STATE_HANDLER_COMPONENT.get()?; + let _ = state_handler.initialize_shard(shard)?; + Ok(()) +} + +/// Initialize the TOP pool author component. +pub fn create_top_pool_author( + connection_registry: Arc, + state_handler: Arc, + ocall_api: Arc, + shielding_key_repository: Arc, +) -> Arc { + let response_channel = Arc::new(RpcResponseChannel::default()); + let rpc_responder = Arc::new(EnclaveRpcResponder::new(connection_registry, response_channel)); + + let side_chain_api = Arc::new(EnclaveSidechainApi::new()); + let top_pool = + Arc::new(EnclaveTopPool::create(PoolOptions::default(), side_chain_api, rpc_responder)); + + Arc::new(EnclaveTopPoolAuthor::new( + top_pool, + AuthorTopFilter {}, + state_handler, + shielding_key_repository, + ocall_api, + )) +} diff --git a/tee-worker/enclave-runtime/src/initialization/parentchain/common.rs b/tee-worker/enclave-runtime/src/initialization/parentchain/common.rs new file mode 100644 index 0000000000..8cbc9ca364 --- /dev/null +++ b/tee-worker/enclave-runtime/src/initialization/parentchain/common.rs @@ -0,0 +1,129 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +use crate::{ + error::Result, + initialization::{ + global_components::{ + EnclaveExtrinsicsFactory, EnclaveImmediateParentchainBlockImportDispatcher, + EnclaveIndirectCallsExecutor, EnclaveNodeMetadataRepository, + EnclaveOffchainWorkerExecutor, EnclaveParentchainBlockImportDispatcher, + EnclaveParentchainBlockImportQueue, EnclaveParentchainBlockImporter, + EnclaveStfExecutor, EnclaveTriggeredParentchainBlockImportDispatcher, + EnclaveValidatorAccessor, GLOBAL_OCALL_API_COMPONENT, + GLOBAL_SHIELDING_KEY_REPOSITORY_COMPONENT, GLOBAL_STATE_HANDLER_COMPONENT, + GLOBAL_STATE_OBSERVER_COMPONENT, GLOBAL_TOP_POOL_AUTHOR_COMPONENT, + }, + EnclaveStfEnclaveSigner, + }, +}; +use itp_component_container::ComponentGetter; +use itp_nonce_cache::GLOBAL_NONCE_CACHE; +use itp_sgx_crypto::Ed25519Seal; +use itp_sgx_io::StaticSealedIO; +use log::*; +use sp_core::H256; +use std::sync::Arc; + +pub(crate) fn create_parentchain_block_importer( + validator_access: Arc, + stf_executor: Arc, + extrinsics_factory: Arc, + node_metadata_repository: Arc, +) -> Result { + let state_observer = GLOBAL_STATE_OBSERVER_COMPONENT.get()?; + let top_pool_author = GLOBAL_TOP_POOL_AUTHOR_COMPONENT.get()?; + let shielding_key_repository = GLOBAL_SHIELDING_KEY_REPOSITORY_COMPONENT.get()?; + let ocall_api = GLOBAL_OCALL_API_COMPONENT.get()?; + let author_api = GLOBAL_TOP_POOL_AUTHOR_COMPONENT.get()?; + + let stf_enclave_signer = Arc::new(EnclaveStfEnclaveSigner::new( + state_observer, + ocall_api, + shielding_key_repository.clone(), + author_api, + )); + let indirect_calls_executor = Arc::new(EnclaveIndirectCallsExecutor::new( + shielding_key_repository, + stf_enclave_signer, + top_pool_author, + node_metadata_repository, + )); + Ok(EnclaveParentchainBlockImporter::new( + validator_access, + stf_executor, + extrinsics_factory, + indirect_calls_executor, + )) +} + +pub(crate) fn create_extrinsics_factory( + genesis_hash: H256, + node_metadata_repository: Arc, +) -> Result> { + let signer = Ed25519Seal::unseal_from_static_file()?; + + Ok(Arc::new(EnclaveExtrinsicsFactory::new( + genesis_hash, + signer, + GLOBAL_NONCE_CACHE.clone(), + node_metadata_repository, + ))) +} + +pub(crate) fn create_offchain_immediate_import_dispatcher( + stf_executor: Arc, + block_importer: EnclaveParentchainBlockImporter, + validator_access: Arc, + extrinsics_factory: Arc, +) -> Result> { + let state_handler = GLOBAL_STATE_HANDLER_COMPONENT.get()?; + let top_pool_author = GLOBAL_TOP_POOL_AUTHOR_COMPONENT.get()?; + + let offchain_worker_executor = Arc::new(EnclaveOffchainWorkerExecutor::new( + top_pool_author, + stf_executor, + state_handler, + validator_access, + extrinsics_factory, + )); + let immediate_dispatcher = EnclaveImmediateParentchainBlockImportDispatcher::new( + block_importer, + ) + .with_observer(move || { + if let Err(e) = offchain_worker_executor.execute() { + error!("Failed to execute trusted calls: {:?}", e); + } + }); + + Ok(Arc::new(EnclaveParentchainBlockImportDispatcher::new_immediate_dispatcher(Arc::new( + immediate_dispatcher, + )))) +} + +pub(crate) fn create_sidechain_triggered_import_dispatcher( + block_importer: EnclaveParentchainBlockImporter, +) -> Arc { + let parentchain_block_import_queue = EnclaveParentchainBlockImportQueue::default(); + let triggered_dispatcher = EnclaveTriggeredParentchainBlockImportDispatcher::new( + block_importer, + parentchain_block_import_queue, + ); + Arc::new(EnclaveParentchainBlockImportDispatcher::new_triggered_dispatcher(Arc::new( + triggered_dispatcher, + ))) +} diff --git a/tee-worker/enclave-runtime/src/initialization/parentchain/mod.rs b/tee-worker/enclave-runtime/src/initialization/parentchain/mod.rs new file mode 100644 index 0000000000..4e2e40e749 --- /dev/null +++ b/tee-worker/enclave-runtime/src/initialization/parentchain/mod.rs @@ -0,0 +1,39 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +use crate::error::Result; +use codec::Decode; +use itc_parentchain::primitives::ParentchainInitParams; +use itp_settings::worker_mode::ProvideWorkerMode; +use parachain::FullParachainHandler; +use solochain::FullSolochainHandler; +use std::vec::Vec; + +mod common; +pub mod parachain; +pub mod solochain; + +pub(crate) fn init_parentchain_components( + encoded_params: Vec, +) -> Result> { + match ParentchainInitParams::decode(&mut encoded_params.as_slice())? { + ParentchainInitParams::Parachain { params } => + FullParachainHandler::init::(params), + ParentchainInitParams::Solochain { params } => + FullSolochainHandler::init::(params), + } +} diff --git a/tee-worker/enclave-runtime/src/initialization/parentchain/parachain.rs b/tee-worker/enclave-runtime/src/initialization/parentchain/parachain.rs new file mode 100644 index 0000000000..3700264848 --- /dev/null +++ b/tee-worker/enclave-runtime/src/initialization/parentchain/parachain.rs @@ -0,0 +1,111 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +use crate::{ + error::Result, + initialization::{ + global_components::{ + EnclaveExtrinsicsFactory, EnclaveNodeMetadataRepository, EnclaveOCallApi, + EnclaveParentchainBlockImportDispatcher, EnclaveStfExecutor, EnclaveValidatorAccessor, + GLOBAL_FULL_PARACHAIN_HANDLER_COMPONENT, GLOBAL_OCALL_API_COMPONENT, + GLOBAL_STATE_HANDLER_COMPONENT, + }, + parentchain::common::{ + create_extrinsics_factory, create_offchain_immediate_import_dispatcher, + create_parentchain_block_importer, create_sidechain_triggered_import_dispatcher, + }, + }, +}; +use codec::Encode; +use itc_parentchain::light_client::{concurrent_access::ValidatorAccess, LightClientState}; +use itp_component_container::{ComponentGetter, ComponentInitializer}; +use itp_settings::worker_mode::{ProvideWorkerMode, WorkerMode}; +use std::{sync::Arc, vec::Vec}; + +pub use itc_parentchain::primitives::{ParachainBlock, ParachainHeader, ParachainParams}; + +#[derive(Clone)] +pub struct FullParachainHandler { + pub genesis_header: ParachainHeader, + pub node_metadata_repository: Arc, + // FIXME: Probably should be split up into a parentchain dependent executor and one independent. + pub stf_executor: Arc, + pub validator_accessor: Arc, + pub extrinsics_factory: Arc, + pub import_dispatcher: Arc, +} + +impl FullParachainHandler { + pub fn init(params: ParachainParams) -> Result> { + let ocall_api = GLOBAL_OCALL_API_COMPONENT.get()?; + let state_handler = GLOBAL_STATE_HANDLER_COMPONENT.get()?; + let node_metadata_repository = Arc::new(EnclaveNodeMetadataRepository::default()); + + let genesis_header = params.genesis_header.clone(); + + let validator = itc_parentchain::light_client::io::read_or_init_parachain_validator::< + ParachainBlock, + EnclaveOCallApi, + >(params, ocall_api.clone())?; + let latest_header = validator.latest_finalized_header(validator.num_relays())?; + let validator_accessor = Arc::new(EnclaveValidatorAccessor::new(validator)); + + let genesis_hash = + validator_accessor.execute_on_validator(|v| v.genesis_hash(v.num_relays()))?; + + let extrinsics_factory = + create_extrinsics_factory(genesis_hash, node_metadata_repository.clone())?; + + let stf_executor = Arc::new(EnclaveStfExecutor::new( + ocall_api, + state_handler, + node_metadata_repository.clone(), + )); + + let block_importer = create_parentchain_block_importer( + validator_accessor.clone(), + stf_executor.clone(), + extrinsics_factory.clone(), + node_metadata_repository.clone(), + )?; + + let import_dispatcher = match WorkerModeProvider::worker_mode() { + WorkerMode::OffChainWorker => create_offchain_immediate_import_dispatcher( + stf_executor.clone(), + block_importer, + validator_accessor.clone(), + extrinsics_factory.clone(), + )?, + WorkerMode::Sidechain => create_sidechain_triggered_import_dispatcher(block_importer), + WorkerMode::Teeracle => + Arc::new(EnclaveParentchainBlockImportDispatcher::new_empty_dispatcher()), + }; + + let parachain_handler = Arc::new(Self { + genesis_header, + node_metadata_repository, + stf_executor, + validator_accessor, + extrinsics_factory, + import_dispatcher, + }); + + GLOBAL_FULL_PARACHAIN_HANDLER_COMPONENT.initialize(parachain_handler); + + Ok(latest_header.encode()) + } +} diff --git a/tee-worker/enclave-runtime/src/initialization/parentchain/solochain.rs b/tee-worker/enclave-runtime/src/initialization/parentchain/solochain.rs new file mode 100644 index 0000000000..7cf928644d --- /dev/null +++ b/tee-worker/enclave-runtime/src/initialization/parentchain/solochain.rs @@ -0,0 +1,110 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +use crate::{ + error::Result, + initialization::{ + global_components::{ + EnclaveExtrinsicsFactory, EnclaveNodeMetadataRepository, EnclaveOCallApi, + EnclaveParentchainBlockImportDispatcher, EnclaveStfExecutor, EnclaveValidatorAccessor, + GLOBAL_FULL_SOLOCHAIN_HANDLER_COMPONENT, GLOBAL_OCALL_API_COMPONENT, + GLOBAL_STATE_HANDLER_COMPONENT, + }, + parentchain::common::{ + create_extrinsics_factory, create_offchain_immediate_import_dispatcher, + create_parentchain_block_importer, create_sidechain_triggered_import_dispatcher, + }, + }, +}; +use codec::Encode; +use itc_parentchain::light_client::{concurrent_access::ValidatorAccess, LightClientState}; +use itp_component_container::{ComponentGetter, ComponentInitializer}; +use itp_settings::worker_mode::{ProvideWorkerMode, WorkerMode}; +use std::{sync::Arc, vec::Vec}; + +pub use itc_parentchain::primitives::{SolochainBlock, SolochainHeader, SolochainParams}; + +pub struct FullSolochainHandler { + pub genesis_header: SolochainHeader, + pub node_metadata_repository: Arc, + // FIXME: Probably should be split up into a parentchain dependent executor and one independent. + pub stf_executor: Arc, + pub validator_accessor: Arc, + pub extrinsics_factory: Arc, + pub import_dispatcher: Arc, +} + +impl FullSolochainHandler { + pub fn init(params: SolochainParams) -> Result> { + let ocall_api = GLOBAL_OCALL_API_COMPONENT.get()?; + let state_handler = GLOBAL_STATE_HANDLER_COMPONENT.get()?; + let node_metadata_repository = Arc::new(EnclaveNodeMetadataRepository::default()); + + let genesis_header = params.genesis_header.clone(); + + let validator = itc_parentchain::light_client::io::read_or_init_grandpa_validator::< + SolochainBlock, + EnclaveOCallApi, + >(params, ocall_api.clone())?; + let latest_header = validator.latest_finalized_header(validator.num_relays())?; + let validator_accessor = Arc::new(EnclaveValidatorAccessor::new(validator)); + + let genesis_hash = + validator_accessor.execute_on_validator(|v| v.genesis_hash(v.num_relays()))?; + + let extrinsics_factory = + create_extrinsics_factory(genesis_hash, node_metadata_repository.clone())?; + + let stf_executor = Arc::new(EnclaveStfExecutor::new( + ocall_api, + state_handler, + node_metadata_repository.clone(), + )); + + let block_importer = create_parentchain_block_importer( + validator_accessor.clone(), + stf_executor.clone(), + extrinsics_factory.clone(), + node_metadata_repository.clone(), + )?; + + let import_dispatcher = match WorkerModeProvider::worker_mode() { + WorkerMode::OffChainWorker => create_offchain_immediate_import_dispatcher( + stf_executor.clone(), + block_importer, + validator_accessor.clone(), + extrinsics_factory.clone(), + )?, + WorkerMode::Sidechain => create_sidechain_triggered_import_dispatcher(block_importer), + WorkerMode::Teeracle => + Arc::new(EnclaveParentchainBlockImportDispatcher::new_empty_dispatcher()), + }; + + let solochain_handler = Arc::new(Self { + genesis_header, + node_metadata_repository, + stf_executor, + validator_accessor, + extrinsics_factory, + import_dispatcher, + }); + + GLOBAL_FULL_SOLOCHAIN_HANDLER_COMPONENT.initialize(solochain_handler); + + Ok(latest_header.encode()) + } +} diff --git a/tee-worker/enclave-runtime/src/ipfs.rs b/tee-worker/enclave-runtime/src/ipfs.rs new file mode 100644 index 0000000000..7b90f062c1 --- /dev/null +++ b/tee-worker/enclave-runtime/src/ipfs.rs @@ -0,0 +1,99 @@ +use cid::{Cid, Result as CidResult}; +use ipfs_unixfs::file::adder::FileAdder; +use log::*; +use multibase::Base; +use std::{convert::TryFrom, vec::Vec}; + +pub struct IpfsContent { + pub cid: CidResult, + pub file_content: Vec, + pub stats: Stats, +} +#[derive(Debug, PartialEq)] +pub enum IpfsError { + InputCidInvalid, + FinalCidMissing, + Verification, +} + +impl IpfsContent { + pub fn new(_cid: &str, _content: Vec) -> IpfsContent { + IpfsContent { cid: Cid::try_from(_cid), file_content: _content, stats: Stats::default() } + } + + pub fn verify(&mut self) -> Result<(), IpfsError> { + let mut adder: FileAdder = FileAdder::default(); + let mut total: usize = 0; + while total < self.file_content.len() { + let (blocks, consumed) = adder.push(&self.file_content[total..]); + total += consumed; + self.stats.process(blocks); + } + let blocks = adder.finish(); + self.stats.process(blocks); + + if let Some(last_cid) = self.stats.last.as_ref() { + let cid_str = Base::Base58Btc.encode(last_cid.hash().as_bytes()); + info!( + "new cid: {} generated from {} blocks, total of {} bytes", + cid_str, self.stats.blocks, self.stats.block_bytes + ); + match self.cid.as_ref() { + Ok(initial_cid) => + if last_cid.hash().eq(&initial_cid.hash()) { + Ok(()) + } else { + Err(IpfsError::Verification) + }, + Err(_) => Err(IpfsError::InputCidInvalid), + } + } else { + Err(IpfsError::FinalCidMissing) + } + } +} +#[derive(Default)] +pub struct Stats { + pub blocks: usize, + pub block_bytes: u64, + pub last: Option, +} + +impl Stats { + fn process)>>(&mut self, new_blocks: I) { + for (cid, block) in new_blocks { + self.last = Some(cid); + self.blocks += 1; + self.block_bytes += block.len() as u64; + } + } +} + +#[allow(unused)] +pub fn test_creates_ipfs_content_struct_works() { + let cid = "QmSaFjwJ2QtS3rZDKzC98XEzv2bqT4TfpWLCpphPPwyQTr"; + let content: Vec = vec![20; 512 * 1024]; + let ipfs_content = IpfsContent::new(cid, content.clone()); + + let cid_str = Base::Base58Btc.encode(ipfs_content.cid.as_ref().unwrap().hash().as_bytes()); + assert_eq!(cid_str, cid); + assert_eq!(ipfs_content.file_content, content); +} + +#[allow(unused)] +pub fn test_verification_ok_for_correct_content() { + let cid = "QmSaFjwJ2QtS3rZDKzC98XEzv2bqT4TfpWLCpphPPwyQTr"; + let content: Vec = vec![20; 512 * 1024]; + let mut ipfs_content = IpfsContent::new(cid, content); + let verification = ipfs_content.verify(); + assert!(verification.is_ok()); +} + +#[allow(unused)] +pub fn test_verification_fails_for_incorrect_content() { + let cid = "QmSaFjwJ2QtS3rZDKzC98XEzv2bqT4TfpWLCpphPPwyQTr"; + let content: Vec = vec![10; 512 * 1024]; + let mut ipfs_content = IpfsContent::new(cid, content); + let verification = ipfs_content.verify(); + assert_eq!(verification.unwrap_err(), IpfsError::Verification); +} diff --git a/tee-worker/enclave-runtime/src/lib.rs b/tee-worker/enclave-runtime/src/lib.rs new file mode 100644 index 0000000000..31d74b3cb5 --- /dev/null +++ b/tee-worker/enclave-runtime/src/lib.rs @@ -0,0 +1,405 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ +#![feature(structural_match)] +#![feature(rustc_attrs)] +#![feature(core_intrinsics)] +#![feature(derive_eq)] +#![feature(trait_alias)] +#![crate_name = "enclave_runtime"] +#![crate_type = "staticlib"] +#![cfg_attr(not(target_env = "sgx"), no_std)] +#![cfg_attr(target_env = "sgx", feature(rustc_private))] +#![allow(clippy::missing_safety_doc)] + +#[cfg(not(target_env = "sgx"))] +#[macro_use] +extern crate sgx_tstd as std; + +use crate::{ + error::{Error, Result}, + initialization::global_components::{ + GLOBAL_FULL_PARACHAIN_HANDLER_COMPONENT, GLOBAL_FULL_SOLOCHAIN_HANDLER_COMPONENT, + GLOBAL_SIDECHAIN_IMPORT_QUEUE_COMPONENT, GLOBAL_STATE_HANDLER_COMPONENT, + }, + rpc::worker_api_direct::sidechain_io_handler, + utils::{ + get_node_metadata_repository_from_solo_or_parachain, + get_triggered_dispatcher_from_solo_or_parachain, utf8_str_from_raw, DecodeRaw, + }, +}; +use codec::{alloc::string::String, Decode}; +use itc_parentchain::block_import_dispatcher::{ + triggered_dispatcher::TriggerParentchainBlockImport, DispatchBlockImport, +}; +use itp_block_import_queue::PushToBlockQueue; +use itp_component_container::ComponentGetter; +use itp_node_api::metadata::NodeMetadata; +use itp_nonce_cache::{MutateNonce, Nonce, GLOBAL_NONCE_CACHE}; +use itp_settings::worker_mode::{ProvideWorkerMode, WorkerMode, WorkerModeProvider}; +use itp_sgx_crypto::{ed25519, Ed25519Seal, Rsa3072Seal}; +use itp_sgx_io::StaticSealedIO; +use itp_types::{ShardIdentifier, SignedBlock}; +use itp_utils::write_slice_and_whitespace_pad; +use log::*; +use sgx_types::sgx_status_t; +use sp_core::crypto::Pair; +use std::{boxed::Box, slice, vec::Vec}; + +mod attestation; +mod empty_impls; +mod initialization; +mod ipfs; +mod ocall; +mod stf_task_handler; +mod utils; + +pub mod error; +pub mod rpc; +mod sync; +mod tls_ra; +pub mod top_pool_execution; + +#[cfg(feature = "teeracle")] +pub mod teeracle; + +#[cfg(feature = "test")] +pub mod test; + +pub type Hash = sp_core::H256; +pub type AuthorityPair = sp_core::ed25519::Pair; + +/// Initialize the enclave. +#[no_mangle] +pub unsafe extern "C" fn init( + mu_ra_addr: *const u8, + mu_ra_addr_size: u32, + untrusted_worker_addr: *const u8, + untrusted_worker_addr_size: u32, +) -> sgx_status_t { + let mu_ra_url = + match String::decode(&mut slice::from_raw_parts(mu_ra_addr, mu_ra_addr_size as usize)) + .map_err(Error::Codec) + { + Ok(addr) => addr, + Err(e) => return e.into(), + }; + + let untrusted_worker_url = match String::decode(&mut slice::from_raw_parts( + untrusted_worker_addr, + untrusted_worker_addr_size as usize, + )) + .map_err(Error::Codec) + { + Ok(addr) => addr, + Err(e) => return e.into(), + }; + + match initialization::init_enclave(mu_ra_url, untrusted_worker_url) { + Err(e) => e.into(), + Ok(()) => sgx_status_t::SGX_SUCCESS, + } +} + +#[no_mangle] +pub unsafe extern "C" fn get_rsa_encryption_pubkey( + pubkey: *mut u8, + pubkey_size: u32, +) -> sgx_status_t { + let rsa_pubkey = match Rsa3072Seal::unseal_pubkey() { + Ok(key) => key, + Err(e) => return e.into(), + }; + + let rsa_pubkey_json = match serde_json::to_string(&rsa_pubkey) { + Ok(k) => k, + Err(x) => { + println!("[Enclave] can't serialize rsa_pubkey {:?} {}", rsa_pubkey, x); + return sgx_status_t::SGX_ERROR_UNEXPECTED + }, + }; + + let pubkey_slice = slice::from_raw_parts_mut(pubkey, pubkey_size as usize); + + if let Err(e) = + write_slice_and_whitespace_pad(pubkey_slice, rsa_pubkey_json.as_bytes().to_vec()) + { + return Error::Other(Box::new(e)).into() + }; + + sgx_status_t::SGX_SUCCESS +} + +#[no_mangle] +pub unsafe extern "C" fn get_ecc_signing_pubkey(pubkey: *mut u8, pubkey_size: u32) -> sgx_status_t { + if let Err(e) = ed25519::create_sealed_if_absent().map_err(Error::Crypto) { + return e.into() + } + + let signer = match Ed25519Seal::unseal_from_static_file().map_err(Error::Crypto) { + Ok(pair) => pair, + Err(e) => return e.into(), + }; + debug!("Restored ECC pubkey: {:?}", signer.public()); + + let pubkey_slice = slice::from_raw_parts_mut(pubkey, pubkey_size as usize); + pubkey_slice.clone_from_slice(&signer.public()); + + sgx_status_t::SGX_SUCCESS +} + +#[no_mangle] +pub unsafe extern "C" fn set_nonce(nonce: *const u32) -> sgx_status_t { + log::info!("[Ecall Set Nonce] Setting the nonce of the enclave to: {}", *nonce); + + let mut nonce_lock = match GLOBAL_NONCE_CACHE.load_for_mutation() { + Ok(l) => l, + Err(e) => { + error!("Failed to set nonce in enclave: {:?}", e); + return sgx_status_t::SGX_ERROR_UNEXPECTED + }, + }; + + *nonce_lock = Nonce(*nonce); + + sgx_status_t::SGX_SUCCESS +} + +#[no_mangle] +pub unsafe extern "C" fn set_node_metadata( + node_metadata: *const u8, + node_metadata_size: u32, +) -> sgx_status_t { + let mut node_metadata_slice = slice::from_raw_parts(node_metadata, node_metadata_size as usize); + let metadata = match NodeMetadata::decode(&mut node_metadata_slice).map_err(Error::Codec) { + Err(e) => { + error!("Failed to decode node metadata: {:?}", e); + return sgx_status_t::SGX_ERROR_UNEXPECTED + }, + Ok(m) => m, + }; + + let node_metadata_repository = match get_node_metadata_repository_from_solo_or_parachain() { + Ok(r) => r, + Err(e) => { + error!("Component get failure: {:?}", e); + return sgx_status_t::SGX_ERROR_UNEXPECTED + }, + }; + + node_metadata_repository.set_metadata(metadata); + info!("Successfully set the node meta data"); + + sgx_status_t::SGX_SUCCESS +} + +/// This is reduced to the sidechain block import RPC interface (i.e. worker-worker communication). +/// The entire rest of the RPC server is run inside the enclave and does not use this e-call function anymore. +#[no_mangle] +pub unsafe extern "C" fn call_rpc_methods( + request: *const u8, + request_len: u32, + response: *mut u8, + response_len: u32, +) -> sgx_status_t { + let request = match utf8_str_from_raw(request, request_len as usize) { + Ok(req) => req, + Err(e) => { + error!("[SidechainRpc] FFI: Invalid utf8 request: {:?}", e); + return sgx_status_t::SGX_ERROR_UNEXPECTED + }, + }; + + let res = match sidechain_rpc_int(request) { + Ok(res) => res, + Err(e) => { + error!("RPC request failed: {:?}", e); + return e.into() + }, + }; + + let response_slice = slice::from_raw_parts_mut(response, response_len as usize); + if let Err(e) = write_slice_and_whitespace_pad(response_slice, res.into_bytes()) { + return Error::Other(Box::new(e)).into() + }; + + sgx_status_t::SGX_SUCCESS +} + +fn sidechain_rpc_int(request: &str) -> Result { + let sidechain_block_import_queue = GLOBAL_SIDECHAIN_IMPORT_QUEUE_COMPONENT.get()?; + + let io = sidechain_io_handler(move |signed_block| { + sidechain_block_import_queue.push_single(signed_block) + }); + + // note: errors are still returned as Option + Ok(io + .handle_request_sync(request) + .unwrap_or_else(|| format!("Empty rpc response for request: {}", request))) +} + +/// Initialize sidechain enclave components. +/// +/// Call this once at startup. Has to be called AFTER the light-client +/// (parentchain components) have been initialized (because we need the parentchain +/// block import dispatcher). +#[no_mangle] +pub unsafe extern "C" fn init_enclave_sidechain_components() -> sgx_status_t { + if let Err(e) = initialization::init_enclave_sidechain_components() { + error!("Failed to initialize sidechain components: {:?}", e); + return sgx_status_t::SGX_ERROR_UNEXPECTED + } + + sgx_status_t::SGX_SUCCESS +} + +/// Call this once at worker startup to initialize the TOP pool and direct invocation RPC server. +/// +/// This function will run the RPC server on the same thread as it is called and will loop there. +/// That means that this function will not return as long as the RPC server is running. The calling +/// code should therefore spawn a new thread when calling this function. +#[no_mangle] +pub unsafe extern "C" fn init_direct_invocation_server( + server_addr: *const u8, + server_addr_size: usize, +) -> sgx_status_t { + let mut server_addr_encoded = slice::from_raw_parts(server_addr, server_addr_size); + + let server_addr = match String::decode(&mut server_addr_encoded) { + Ok(s) => s, + Err(e) => { + error!("Decoding RPC server address failed. Error: {:?}", e); + return sgx_status_t::SGX_ERROR_UNEXPECTED + }, + }; + + if let Err(e) = initialization::init_direct_invocation_server(server_addr) { + error!("Failed to initialize direct invocation server: {:?}", e); + return sgx_status_t::SGX_ERROR_UNEXPECTED + } + + sgx_status_t::SGX_SUCCESS +} + +#[no_mangle] +pub unsafe extern "C" fn init_parentchain_components( + params: *const u8, + params_size: usize, + latest_header: *mut u8, + latest_header_size: usize, +) -> sgx_status_t { + info!("Initializing light client!"); + + let encoded_params = slice::from_raw_parts(params, params_size); + let latest_header_slice = slice::from_raw_parts_mut(latest_header, latest_header_size); + + let encoded_latest_header = match initialization::parentchain::init_parentchain_components::< + WorkerModeProvider, + >(encoded_params.to_vec()) + { + Ok(h) => h, + Err(e) => return e.into(), + }; + + if let Err(e) = write_slice_and_whitespace_pad(latest_header_slice, encoded_latest_header) { + return Error::Other(Box::new(e)).into() + }; + + sgx_status_t::SGX_SUCCESS +} + +#[no_mangle] +pub unsafe extern "C" fn init_shard(shard: *const u8, shard_size: u32) -> sgx_status_t { + let shard_identifier = + ShardIdentifier::from_slice(slice::from_raw_parts(shard, shard_size as usize)); + + if let Err(e) = initialization::init_shard(shard_identifier) { + error!("Failed to initialize shard ({:?}): {:?}", shard_identifier, e); + return sgx_status_t::SGX_ERROR_UNEXPECTED + } + + sgx_status_t::SGX_SUCCESS +} + +#[no_mangle] +pub unsafe extern "C" fn sync_parentchain( + blocks_to_sync: *const u8, + blocks_to_sync_size: usize, + _nonce: *const u32, +) -> sgx_status_t { + let blocks_to_sync = match Vec::::decode_raw(blocks_to_sync, blocks_to_sync_size) { + Ok(blocks) => blocks, + Err(e) => return Error::Codec(e).into(), + }; + + if let Err(e) = dispatch_parentchain_blocks_for_import::(blocks_to_sync) { + return e.into() + } + + sgx_status_t::SGX_SUCCESS +} + +/// Dispatch the parentchain blocks for import. +/// Depending on the worker mode, a different dispatcher is used: +/// +/// * An immediate dispatcher will immediately import any parentchain blocks and execute +/// the corresponding extrinsics (offchain-worker executor). +/// * The sidechain uses a triggered dispatcher, where the import of a parentchain block is +/// synchronized and triggered by the sidechain block production cycle. +/// +fn dispatch_parentchain_blocks_for_import( + blocks_to_sync: Vec, +) -> Result<()> { + if WorkerModeProvider::worker_mode() == WorkerMode::Teeracle { + trace!("Not importing any parentchain blocks"); + return Ok(()) + } + + let import_dispatcher = + if let Ok(solochain_handler) = GLOBAL_FULL_SOLOCHAIN_HANDLER_COMPONENT.get() { + solochain_handler.import_dispatcher.clone() + } else if let Ok(parachain_handler) = GLOBAL_FULL_PARACHAIN_HANDLER_COMPONENT.get() { + parachain_handler.import_dispatcher.clone() + } else { + return Err(Error::NoParentchainAssigned) + }; + + import_dispatcher.dispatch_import(blocks_to_sync)?; + Ok(()) +} + +/// Triggers the import of parentchain blocks when using a queue to sync parentchain block import +/// with sidechain block production. +/// +/// This trigger is only useful in combination with a `TriggeredDispatcher` and sidechain. In case no +/// sidechain and the `ImmediateDispatcher` are used, this function is obsolete. +#[no_mangle] +pub unsafe extern "C" fn trigger_parentchain_block_import() -> sgx_status_t { + match internal_trigger_parentchain_block_import() { + Ok(()) => sgx_status_t::SGX_SUCCESS, + Err(e) => { + error!("Failed to trigger import of parentchain blocks: {:?}", e); + sgx_status_t::SGX_ERROR_UNEXPECTED + }, + } +} + +fn internal_trigger_parentchain_block_import() -> Result<()> { + let triggered_import_dispatcher = get_triggered_dispatcher_from_solo_or_parachain()?; + triggered_import_dispatcher.import_all()?; + Ok(()) +} diff --git a/tee-worker/enclave-runtime/src/ocall/attestation_ocall.rs b/tee-worker/enclave-runtime/src/ocall/attestation_ocall.rs new file mode 100644 index 0000000000..11c26a5aa1 --- /dev/null +++ b/tee-worker/enclave-runtime/src/ocall/attestation_ocall.rs @@ -0,0 +1,183 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +use crate::ocall::{ffi, OcallApi}; +use frame_support::ensure; +use itp_ocall_api::EnclaveAttestationOCallApi; +use log::*; +use sgx_tse::rsgx_create_report; +use sgx_types::{ + sgx_epid_group_id_t, sgx_measurement_t, sgx_platform_info_t, sgx_quote_nonce_t, + sgx_quote_sign_type_t, sgx_report_body_t, sgx_report_data_t, sgx_report_t, sgx_spid_t, + sgx_status_t, sgx_target_info_t, sgx_update_info_bit_t, SgxResult, +}; +use std::{ptr, vec::Vec}; + +const RET_QUOTE_BUF_LEN: usize = 2048; + +impl EnclaveAttestationOCallApi for OcallApi { + fn sgx_init_quote(&self) -> SgxResult<(sgx_target_info_t, sgx_epid_group_id_t)> { + let mut ti: sgx_target_info_t = sgx_target_info_t::default(); + let mut eg: sgx_epid_group_id_t = sgx_epid_group_id_t::default(); + let mut rt: sgx_status_t = sgx_status_t::SGX_ERROR_UNEXPECTED; + + let res = unsafe { + ffi::ocall_sgx_init_quote( + &mut rt as *mut sgx_status_t, + &mut ti as *mut sgx_target_info_t, + &mut eg as *mut sgx_epid_group_id_t, + ) + }; + + ensure!(res == sgx_status_t::SGX_SUCCESS, res); + ensure!(rt == sgx_status_t::SGX_SUCCESS, rt); + + Ok((ti, eg)) + } + + fn get_ias_socket(&self) -> SgxResult { + let mut rt: sgx_status_t = sgx_status_t::SGX_ERROR_UNEXPECTED; + let mut ias_sock: i32 = 0; + + let res = unsafe { + ffi::ocall_get_ias_socket(&mut rt as *mut sgx_status_t, &mut ias_sock as *mut i32) + }; + + ensure!(res == sgx_status_t::SGX_SUCCESS, res); + ensure!(rt == sgx_status_t::SGX_SUCCESS, rt); + + Ok(ias_sock) + } + + fn get_quote( + &self, + sig_rl: Vec, + report: sgx_report_t, + sign_type: sgx_quote_sign_type_t, + spid: sgx_spid_t, + quote_nonce: sgx_quote_nonce_t, + ) -> SgxResult<(sgx_report_t, Vec)> { + let mut qe_report = sgx_report_t::default(); + let mut return_quote_buf = [0u8; RET_QUOTE_BUF_LEN]; + let mut quote_len: u32 = 0; + + let (p_sigrl, sigrl_len) = if sig_rl.is_empty() { + (ptr::null(), 0) + } else { + (sig_rl.as_ptr(), sig_rl.len() as u32) + }; + let p_report = &report as *const sgx_report_t; + let quote_type = sign_type; + + let mut rt: sgx_status_t = sgx_status_t::SGX_ERROR_UNEXPECTED; + let p_spid = &spid as *const sgx_spid_t; + let p_nonce = "e_nonce as *const sgx_quote_nonce_t; + let p_qe_report = &mut qe_report as *mut sgx_report_t; + let p_quote = return_quote_buf.as_mut_ptr(); + let maxlen = RET_QUOTE_BUF_LEN as u32; + let p_quote_len = &mut quote_len as *mut u32; + + let result = unsafe { + ffi::ocall_get_quote( + &mut rt as *mut sgx_status_t, + p_sigrl, + sigrl_len, + p_report, + quote_type, + p_spid, + p_nonce, + p_qe_report, + p_quote, + maxlen, + p_quote_len, + ) + }; + + ensure!(result == sgx_status_t::SGX_SUCCESS, result); + ensure!(rt == sgx_status_t::SGX_SUCCESS, rt); + + let quote_vec: Vec = Vec::from(&return_quote_buf[..quote_len as usize]); + + Ok((qe_report, quote_vec)) + } + + fn get_update_info( + &self, + platform_info: sgx_platform_info_t, + enclave_trusted: i32, + ) -> SgxResult { + let mut rt: sgx_status_t = sgx_status_t::SGX_ERROR_UNEXPECTED; + let mut update_info = sgx_update_info_bit_t::default(); + + let result = unsafe { + ffi::ocall_get_update_info( + &mut rt as *mut sgx_status_t, + &platform_info as *const sgx_platform_info_t, + enclave_trusted, + &mut update_info as *mut sgx_update_info_bit_t, + ) + }; + + // debug logging + if rt != sgx_status_t::SGX_SUCCESS { + warn!("ocall_get_update_info unsuccessful. rt={:?}", rt); + // Curly braces to copy `unaligned_references` of packed fields into properly aligned temporary: + // https://github.com/rust-lang/rust/issues/82523 + debug!("update_info.pswUpdate: {}", { update_info.pswUpdate }); + debug!("update_info.csmeFwUpdate: {}", { update_info.csmeFwUpdate }); + debug!("update_info.ucodeUpdate: {}", { update_info.ucodeUpdate }); + } + + ensure!(result == sgx_status_t::SGX_SUCCESS, result); + ensure!(rt == sgx_status_t::SGX_SUCCESS, rt); + + Ok(update_info) + } + + fn get_mrenclave_of_self(&self) -> SgxResult { + Ok(self.get_report_of_self()?.mr_enclave) + } +} + +trait GetSgxReport { + fn get_report_of_self(&self) -> SgxResult; +} + +impl GetSgxReport for T { + fn get_report_of_self(&self) -> SgxResult { + // (1) get ti + eg + let init_quote_result = self.sgx_init_quote()?; + + let target_info = init_quote_result.0; + let report_data: sgx_report_data_t = sgx_report_data_t::default(); + + let rep = match rsgx_create_report(&target_info, &report_data) { + Ok(r) => { + debug!( + " [Enclave] Report creation successful. mr_signer.m = {:?}", + r.body.mr_signer.m + ); + r + }, + Err(e) => { + error!(" [Enclave] Report creation failed. {:?}", e); + return Err(e) + }, + }; + Ok(rep.body) + } +} diff --git a/tee-worker/enclave-runtime/src/ocall/ffi.rs b/tee-worker/enclave-runtime/src/ocall/ffi.rs new file mode 100644 index 0000000000..21ee8ce5a3 --- /dev/null +++ b/tee-worker/enclave-runtime/src/ocall/ffi.rs @@ -0,0 +1,110 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +use sgx_types::{ + sgx_epid_group_id_t, sgx_platform_info_t, sgx_quote_nonce_t, sgx_quote_sign_type_t, + sgx_report_t, sgx_spid_t, sgx_status_t, sgx_target_info_t, sgx_update_info_bit_t, +}; + +extern "C" { + pub fn ocall_sgx_init_quote( + ret_val: *mut sgx_status_t, + ret_ti: *mut sgx_target_info_t, + ret_gid: *mut sgx_epid_group_id_t, + ) -> sgx_status_t; + + pub fn ocall_get_ias_socket(ret_val: *mut sgx_status_t, ret_fd: *mut i32) -> sgx_status_t; + + pub fn ocall_get_quote( + ret_val: *mut sgx_status_t, + p_sigrl: *const u8, + sigrl_len: u32, + p_report: *const sgx_report_t, + quote_type: sgx_quote_sign_type_t, + p_spid: *const sgx_spid_t, + p_nonce: *const sgx_quote_nonce_t, + p_qe_report: *mut sgx_report_t, + p_quote: *mut u8, + maxlen: u32, + p_quote_len: *mut u32, + ) -> sgx_status_t; + + pub fn ocall_get_update_info( + ret_val: *mut sgx_status_t, + platform_blob: *const sgx_platform_info_t, + enclave_trusted: i32, + update_info: *mut sgx_update_info_bit_t, + ) -> sgx_status_t; + + pub fn ocall_worker_request( + ret_val: *mut sgx_status_t, + request: *const u8, + req_size: u32, + response: *mut u8, + resp_size: u32, + ) -> sgx_status_t; + + pub fn ocall_update_metric( + ret_val: *mut sgx_status_t, + metric_ptr: *const u8, + metric_size: u32, + ) -> sgx_status_t; + + pub fn ocall_propose_sidechain_blocks( + ret_val: *mut sgx_status_t, + signed_blocks: *const u8, + signed_blocks_size: u32, + ) -> sgx_status_t; + + pub fn ocall_store_sidechain_blocks( + ret_val: *mut sgx_status_t, + signed_blocks: *const u8, + signed_blocks_size: u32, + ) -> sgx_status_t; + + pub fn ocall_fetch_sidechain_blocks_from_peer( + ret_val: *mut sgx_status_t, + last_imported_block_hash: *const u8, + last_imported_block_hash_size: u32, + maybe_until_block_hash: *const u8, + maybe_until_block_hash_encoded_size: u32, + shard_identifier: *const u8, + shard_identifier_size: u32, + sidechain_blocks: *mut u8, + sidechain_blocks_size: u32, + ) -> sgx_status_t; + + pub fn ocall_send_to_parentchain( + ret_val: *mut sgx_status_t, + extrinsics: *const u8, + extrinsics_size: u32, + ) -> sgx_status_t; + + pub fn ocall_read_ipfs( + ret_val: *mut sgx_status_t, + cid: *const u8, + cid_size: u32, + ) -> sgx_status_t; + + pub fn ocall_write_ipfs( + ret_val: *mut sgx_status_t, + enc_state: *const u8, + enc_state_size: u32, + cid: *mut u8, + cid_size: u32, + ) -> sgx_status_t; +} diff --git a/tee-worker/enclave-runtime/src/ocall/ipfs_ocall.rs b/tee-worker/enclave-runtime/src/ocall/ipfs_ocall.rs new file mode 100644 index 0000000000..d1a5530856 --- /dev/null +++ b/tee-worker/enclave-runtime/src/ocall/ipfs_ocall.rs @@ -0,0 +1,57 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + Copyright (C) 2017-2019 Baidu, Inc. All Rights Reserved. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +use crate::ocall::{ffi, OcallApi}; +use frame_support::ensure; +use itp_ocall_api::{EnclaveIpfsOCallApi, IpfsCid}; +use sgx_types::{sgx_status_t, SgxResult}; + +impl EnclaveIpfsOCallApi for OcallApi { + fn write_ipfs(&self, encoded_state: &[u8]) -> SgxResult { + let mut rt: sgx_status_t = sgx_status_t::SGX_ERROR_UNEXPECTED; + let mut cid_buf = IpfsCid([0u8; 46]); + + let res = unsafe { + ffi::ocall_write_ipfs( + &mut rt as *mut sgx_status_t, + encoded_state.as_ptr(), + encoded_state.len() as u32, + cid_buf.0.as_mut_ptr(), + cid_buf.0.len() as u32, + ) + }; + + ensure!(rt == sgx_status_t::SGX_SUCCESS, rt); + ensure!(res == sgx_status_t::SGX_SUCCESS, res); + + Ok(cid_buf) + } + + fn read_ipfs(&self, cid: &IpfsCid) -> SgxResult<()> { + let mut rt: sgx_status_t = sgx_status_t::SGX_ERROR_UNEXPECTED; + + let res = unsafe { + ffi::ocall_read_ipfs(&mut rt as *mut sgx_status_t, cid.0.as_ptr(), cid.0.len() as u32) + }; + + ensure!(rt == sgx_status_t::SGX_SUCCESS, rt); + ensure!(res == sgx_status_t::SGX_SUCCESS, res); + + Ok(()) + } +} diff --git a/tee-worker/enclave-runtime/src/ocall/metrics_ocall.rs b/tee-worker/enclave-runtime/src/ocall/metrics_ocall.rs new file mode 100644 index 0000000000..0d12dfd7d6 --- /dev/null +++ b/tee-worker/enclave-runtime/src/ocall/metrics_ocall.rs @@ -0,0 +1,42 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +use crate::ocall::{ffi, OcallApi}; +use codec::Encode; +use frame_support::ensure; +use itp_ocall_api::EnclaveMetricsOCallApi; +use sgx_types::{sgx_status_t, SgxResult}; + +impl EnclaveMetricsOCallApi for OcallApi { + fn update_metric(&self, metric: Metric) -> SgxResult<()> { + let mut rt: sgx_status_t = sgx_status_t::SGX_ERROR_UNEXPECTED; + let metric_encoded = metric.encode(); + + let res = unsafe { + ffi::ocall_update_metric( + &mut rt as *mut sgx_status_t, + metric_encoded.as_ptr(), + metric_encoded.len() as u32, + ) + }; + + ensure!(rt == sgx_status_t::SGX_SUCCESS, rt); + ensure!(res == sgx_status_t::SGX_SUCCESS, res); + + Ok(()) + } +} diff --git a/tee-worker/enclave-runtime/src/ocall/mod.rs b/tee-worker/enclave-runtime/src/ocall/mod.rs new file mode 100644 index 0000000000..7374b63fde --- /dev/null +++ b/tee-worker/enclave-runtime/src/ocall/mod.rs @@ -0,0 +1,26 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +mod attestation_ocall; +mod ffi; +mod ipfs_ocall; +mod metrics_ocall; +mod on_chain_ocall; +mod sidechain_ocall; + +#[derive(Clone, Debug, Default)] +pub struct OcallApi; diff --git a/tee-worker/enclave-runtime/src/ocall/on_chain_ocall.rs b/tee-worker/enclave-runtime/src/ocall/on_chain_ocall.rs new file mode 100644 index 0000000000..913a0a7d53 --- /dev/null +++ b/tee-worker/enclave-runtime/src/ocall/on_chain_ocall.rs @@ -0,0 +1,109 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + Copyright (C) 2017-2019 Baidu, Inc. All Rights Reserved. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +use crate::ocall::{ffi, OcallApi}; +use codec::{Decode, Encode}; +use frame_support::ensure; +use itp_ocall_api::{EnclaveOnChainOCallApi, Result}; +use itp_storage::{verify_storage_entries, Error as StorageError}; +use itp_types::{storage::StorageEntryVerified, WorkerRequest, WorkerResponse, H256}; +use log::*; +use sgx_types::*; +use sp_runtime::{traits::Header, OpaqueExtrinsic}; +use std::vec::Vec; + +impl EnclaveOnChainOCallApi for OcallApi { + fn send_to_parentchain(&self, extrinsics: Vec) -> SgxResult<()> { + let mut rt: sgx_status_t = sgx_status_t::SGX_ERROR_UNEXPECTED; + let extrinsics_encoded = extrinsics.encode(); + + let res = unsafe { + ffi::ocall_send_to_parentchain( + &mut rt as *mut sgx_status_t, + extrinsics_encoded.as_ptr(), + extrinsics_encoded.len() as u32, + ) + }; + + ensure!(rt == sgx_status_t::SGX_SUCCESS, rt); + ensure!(res == sgx_status_t::SGX_SUCCESS, res); + + Ok(()) + } + + fn worker_request( + &self, + req: Vec, + ) -> SgxResult>> { + let mut rt: sgx_status_t = sgx_status_t::SGX_ERROR_UNEXPECTED; + let mut resp: Vec = vec![0; 4196 * 4]; + let request_encoded = req.encode(); + + let res = unsafe { + ffi::ocall_worker_request( + &mut rt as *mut sgx_status_t, + request_encoded.as_ptr(), + request_encoded.len() as u32, + resp.as_mut_ptr(), + resp.len() as u32, + ) + }; + + ensure!(rt == sgx_status_t::SGX_SUCCESS, rt); + ensure!(res == sgx_status_t::SGX_SUCCESS, res); + + let decoded_response: Vec> = Decode::decode(&mut resp.as_slice()) + .map_err(|e| { + error!("Failed to decode WorkerResponse: {}", e); + sgx_status_t::SGX_ERROR_UNEXPECTED + })?; + + Ok(decoded_response) + } + + fn get_storage_verified, V: Decode>( + &self, + storage_hash: Vec, + header: &H, + ) -> Result> { + // the code below seems like an overkill, but it is surprisingly difficult to + // get an owned value from a `Vec` without cloning. + Ok(self + .get_multiple_storages_verified(vec![storage_hash], header)? + .into_iter() + .next() + .ok_or(StorageError::StorageValueUnavailable)?) + } + + fn get_multiple_storages_verified, V: Decode>( + &self, + storage_hashes: Vec>, + header: &H, + ) -> Result>> { + let requests = storage_hashes + .into_iter() + .map(|key| WorkerRequest::ChainStorage(key, Some(header.hash()))) + .collect(); + + let storage_entries = self + .worker_request::>(requests) + .map(|storages| verify_storage_entries(storages, header))??; + + Ok(storage_entries) + } +} diff --git a/tee-worker/enclave-runtime/src/ocall/sidechain_ocall.rs b/tee-worker/enclave-runtime/src/ocall/sidechain_ocall.rs new file mode 100644 index 0000000000..d6f578791e --- /dev/null +++ b/tee-worker/enclave-runtime/src/ocall/sidechain_ocall.rs @@ -0,0 +1,112 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + Copyright (C) 2017-2019 Baidu, Inc. All Rights Reserved. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +use crate::ocall::{ffi, OcallApi}; +use codec::{Decode, Encode}; +use frame_support::ensure; +use itp_ocall_api::EnclaveSidechainOCallApi; +use itp_types::{BlockHash, ShardIdentifier}; +use log::*; +use sgx_types::{sgx_status_t, SgxResult}; +use std::vec::Vec; + +impl EnclaveSidechainOCallApi for OcallApi { + fn propose_sidechain_blocks( + &self, + signed_blocks: Vec, + ) -> SgxResult<()> { + let mut rt: sgx_status_t = sgx_status_t::SGX_ERROR_UNEXPECTED; + let signed_blocks_encoded = signed_blocks.encode(); + + let res = unsafe { + ffi::ocall_propose_sidechain_blocks( + &mut rt as *mut sgx_status_t, + signed_blocks_encoded.as_ptr(), + signed_blocks_encoded.len() as u32, + ) + }; + + ensure!(rt == sgx_status_t::SGX_SUCCESS, rt); + ensure!(res == sgx_status_t::SGX_SUCCESS, res); + + Ok(()) + } + + fn store_sidechain_blocks( + &self, + signed_blocks: Vec, + ) -> SgxResult<()> { + let mut rt: sgx_status_t = sgx_status_t::SGX_ERROR_UNEXPECTED; + let signed_blocks_encoded = signed_blocks.encode(); + + let res = unsafe { + ffi::ocall_store_sidechain_blocks( + &mut rt as *mut sgx_status_t, + signed_blocks_encoded.as_ptr(), + signed_blocks_encoded.len() as u32, + ) + }; + + ensure!(rt == sgx_status_t::SGX_SUCCESS, rt); + ensure!(res == sgx_status_t::SGX_SUCCESS, res); + + Ok(()) + } + + fn fetch_sidechain_blocks_from_peer( + &self, + last_imported_block_hash: BlockHash, + maybe_until_block_hash: Option, + shard_identifier: ShardIdentifier, + ) -> SgxResult> { + const BLOCK_BUFFER_SIZE: usize = 262144; // Buffer size for sidechain blocks in bytes (256KB). + + let mut rt: sgx_status_t = sgx_status_t::SGX_ERROR_UNEXPECTED; + let last_imported_block_hash_encoded = last_imported_block_hash.encode(); + let maybe_until_block_hash_encoded = maybe_until_block_hash.encode(); + let shard_identifier_encoded = shard_identifier.encode(); + + // We have to pre-allocate the vector and hope it's large enough (see GitHub issue #621). + let mut signed_blocks_encoded: Vec = vec![0; BLOCK_BUFFER_SIZE]; + + let res = unsafe { + ffi::ocall_fetch_sidechain_blocks_from_peer( + &mut rt as *mut sgx_status_t, + last_imported_block_hash_encoded.as_ptr(), + last_imported_block_hash_encoded.len() as u32, + maybe_until_block_hash_encoded.as_ptr(), + maybe_until_block_hash_encoded.len() as u32, + shard_identifier_encoded.as_ptr(), + shard_identifier_encoded.len() as u32, + signed_blocks_encoded.as_mut_ptr(), + signed_blocks_encoded.len() as u32, + ) + }; + + ensure!(rt == sgx_status_t::SGX_SUCCESS, rt); + ensure!(res == sgx_status_t::SGX_SUCCESS, res); + + let decoded_signed_blocks: Vec = + Decode::decode(&mut signed_blocks_encoded.as_slice()).map_err(|e| { + error!("Failed to decode WorkerResponse: {}", e); + sgx_status_t::SGX_ERROR_UNEXPECTED + })?; + + Ok(decoded_signed_blocks) + } +} diff --git a/tee-worker/enclave-runtime/src/rpc/mod.rs b/tee-worker/enclave-runtime/src/rpc/mod.rs new file mode 100644 index 0000000000..5b359ab270 --- /dev/null +++ b/tee-worker/enclave-runtime/src/rpc/mod.rs @@ -0,0 +1,19 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +pub mod rpc_response_channel; +pub mod worker_api_direct; diff --git a/tee-worker/enclave-runtime/src/rpc/rpc_response_channel.rs b/tee-worker/enclave-runtime/src/rpc/rpc_response_channel.rs new file mode 100644 index 0000000000..7a84fde928 --- /dev/null +++ b/tee-worker/enclave-runtime/src/rpc/rpc_response_channel.rs @@ -0,0 +1,40 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +use crate::initialization::global_components::GLOBAL_WEB_SOCKET_SERVER_COMPONENT; +use itc_direct_rpc_server::{response_channel::ResponseChannel, DirectRpcError}; +use itc_tls_websocket_server::{ConnectionToken, WebSocketResponder}; +use itp_component_container::ComponentGetter; +use std::string::String; + +/// RPC response channel. +/// +/// Uses the web-socket server to send an RPC response/update. +/// In case no server is available or running, the response will be discarded. +#[derive(Default)] +pub struct RpcResponseChannel; + +impl ResponseChannel for RpcResponseChannel { + type Error = DirectRpcError; + + fn respond(&self, token: ConnectionToken, message: String) -> Result<(), Self::Error> { + let web_socket_server = GLOBAL_WEB_SOCKET_SERVER_COMPONENT + .get() + .map_err(|e| DirectRpcError::Other(e.into()))?; + web_socket_server.send_message(token, message).map_err(|e| e.into()) + } +} diff --git a/tee-worker/enclave-runtime/src/rpc/worker_api_direct.rs b/tee-worker/enclave-runtime/src/rpc/worker_api_direct.rs new file mode 100644 index 0000000000..a746f5e2ea --- /dev/null +++ b/tee-worker/enclave-runtime/src/rpc/worker_api_direct.rs @@ -0,0 +1,223 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +use codec::Encode; +use core::result::Result; +use ita_sgx_runtime::Runtime; +use itp_primitives_cache::{GetPrimitives, GLOBAL_PRIMITIVES_CACHE}; +use itp_rpc::RpcReturnValue; +use itp_sgx_crypto::Rsa3072Seal; +use itp_stf_executor::getter_executor::ExecuteGetter; +use itp_top_pool_author::traits::AuthorApi; +use itp_types::{DirectRequestStatus, Request, ShardIdentifier, H256}; +use itp_utils::{FromHexPrefixed, ToHexPrefixed}; +use its_primitives::types::block::SignedBlock; +use its_sidechain::rpc_handler::{direct_top_pool_api, import_block_api}; +use jsonrpc_core::{serde_json::json, IoHandler, Params, Value}; +use std::{borrow::ToOwned, format, str, string::String, sync::Arc, vec::Vec}; + +fn compute_hex_encoded_return_error(error_msg: &str) -> String { + RpcReturnValue::from_error_message(error_msg).to_hex() +} + +fn get_all_rpc_methods_string(io_handler: &IoHandler) -> String { + let method_string = io_handler + .iter() + .map(|rp_tuple| rp_tuple.0.to_owned()) + .collect::>() + .join(", "); + + format!("methods: [{}]", method_string) +} + +pub fn public_api_rpc_handler(top_pool_author: Arc, getter_executor: Arc) -> IoHandler +where + R: AuthorApi + Send + Sync + 'static, + G: ExecuteGetter + Send + Sync + 'static, +{ + let io = IoHandler::new(); + + // Add direct TOP pool rpc methods + let mut io = direct_top_pool_api::add_top_pool_direct_rpc_methods(top_pool_author, io); + + // author_getShieldingKey + let rsa_pubkey_name: &str = "author_getShieldingKey"; + io.add_sync_method(rsa_pubkey_name, move |_: Params| { + let rsa_pubkey = match Rsa3072Seal::unseal_pubkey() { + Ok(key) => key, + Err(status) => { + let error_msg: String = format!("Could not get rsa pubkey due to: {}", status); + return Ok(json!(compute_hex_encoded_return_error(error_msg.as_str()))) + }, + }; + + let rsa_pubkey_json = match serde_json::to_string(&rsa_pubkey) { + Ok(k) => k, + Err(x) => { + let error_msg: String = + format!("[Enclave] can't serialize rsa_pubkey {:?} {}", rsa_pubkey, x); + return Ok(json!(compute_hex_encoded_return_error(error_msg.as_str()))) + }, + }; + let json_value = + RpcReturnValue::new(rsa_pubkey_json.encode(), false, DirectRequestStatus::Ok); + Ok(json!(json_value.to_hex())) + }); + + let mu_ra_url_name: &str = "author_getMuRaUrl"; + io.add_sync_method(mu_ra_url_name, move |_: Params| { + let url = match GLOBAL_PRIMITIVES_CACHE.get_mu_ra_url() { + Ok(url) => url, + Err(status) => { + let error_msg: String = format!("Could not get mu ra url due to: {}", status); + return Ok(json!(compute_hex_encoded_return_error(error_msg.as_str()))) + }, + }; + + let json_value = RpcReturnValue::new(url.encode(), false, DirectRequestStatus::Ok); + Ok(json!(json_value.to_hex())) + }); + + let untrusted_url_name: &str = "author_getUntrustedUrl"; + io.add_sync_method(untrusted_url_name, move |_: Params| { + let url = match GLOBAL_PRIMITIVES_CACHE.get_untrusted_worker_url() { + Ok(url) => url, + Err(status) => { + let error_msg: String = format!("Could not get untrusted url due to: {}", status); + return Ok(json!(compute_hex_encoded_return_error(error_msg.as_str()))) + }, + }; + + let json_value = RpcReturnValue::new(url.encode(), false, DirectRequestStatus::Ok); + Ok(json!(json_value.to_hex())) + }); + + // chain_subscribeAllHeads + let chain_subscribe_all_heads_name: &str = "chain_subscribeAllHeads"; + io.add_sync_method(chain_subscribe_all_heads_name, |_: Params| { + let parsed = "world"; + Ok(Value::String(format!("hello, {}", parsed))) + }); + + // state_getMetadata + let state_get_metadata_name: &str = "state_getMetadata"; + io.add_sync_method(state_get_metadata_name, |_: Params| { + let metadata = Runtime::metadata(); + let json_value = RpcReturnValue::new(metadata.into(), false, DirectRequestStatus::Ok); + Ok(json!(json_value.to_hex())) + }); + + // state_getRuntimeVersion + let state_get_runtime_version_name: &str = "state_getRuntimeVersion"; + io.add_sync_method(state_get_runtime_version_name, |_: Params| { + let parsed = "world"; + Ok(Value::String(format!("hello, {}", parsed))) + }); + + // state_executeGetter + let state_execute_getter_name: &str = "state_executeGetter"; + io.add_sync_method(state_execute_getter_name, move |params: Params| { + let json_value = match execute_getter_inner(getter_executor.as_ref(), params) { + Ok(state_getter_value) => RpcReturnValue { + do_watch: false, + value: state_getter_value.encode(), + status: DirectRequestStatus::Ok, + } + .to_hex(), + Err(error) => compute_hex_encoded_return_error(error.as_str()), + }; + Ok(json!(json_value)) + }); + + // system_health + let state_health_name: &str = "system_health"; + io.add_sync_method(state_health_name, |_: Params| { + let parsed = "world"; + Ok(Value::String(format!("hello, {}", parsed))) + }); + + // system_name + let state_name_name: &str = "system_name"; + io.add_sync_method(state_name_name, |_: Params| { + let parsed = "world"; + Ok(Value::String(format!("hello, {}", parsed))) + }); + + // system_version + let state_version_name: &str = "system_version"; + io.add_sync_method(state_version_name, |_: Params| { + let parsed = "world"; + Ok(Value::String(format!("hello, {}", parsed))) + }); + + // returns all rpcs methods + let rpc_methods_string = get_all_rpc_methods_string(&io); + io.add_sync_method("rpc_methods", move |_: Params| { + Ok(Value::String(rpc_methods_string.to_owned())) + }); + + io +} + +fn execute_getter_inner( + getter_executor: &G, + params: Params, +) -> Result>, String> { + let hex_encoded_params = params.parse::>().map_err(|e| format!("{:?}", e))?; + + let request = + Request::from_hex(&hex_encoded_params[0].clone()).map_err(|e| format!("{:?}", e))?; + + let shard: ShardIdentifier = request.shard; + let encoded_trusted_getter: Vec = request.cyphertext; + + let getter_result = getter_executor + .execute_getter(&shard, encoded_trusted_getter) + .map_err(|e| format!("{:?}", e))?; + + Ok(getter_result) +} + +pub fn sidechain_io_handler(import_fn: ImportFn) -> IoHandler +where + ImportFn: Fn(SignedBlock) -> Result<(), Error> + Sync + Send + 'static, + Error: std::fmt::Debug, +{ + let io = IoHandler::new(); + import_block_api::add_import_block_rpc_method(import_fn, io) +} + +#[cfg(feature = "test")] +pub mod tests { + use super::*; + use std::string::ToString; + + pub fn test_given_io_handler_methods_then_retrieve_all_names_as_string() { + let mut io = IoHandler::new(); + let method_names: [&str; 4] = ["method1", "another_method", "fancy_thing", "solve_all"]; + + for method_name in method_names.iter() { + io.add_sync_method(method_name, |_: Params| Ok(Value::String("".to_string()))); + } + + let method_string = get_all_rpc_methods_string(&io); + + for method_name in method_names.iter() { + assert!(method_string.contains(method_name)); + } + } +} diff --git a/tee-worker/enclave-runtime/src/stf_task_handler.rs b/tee-worker/enclave-runtime/src/stf_task_handler.rs new file mode 100644 index 0000000000..8ca4508ff1 --- /dev/null +++ b/tee-worker/enclave-runtime/src/stf_task_handler.rs @@ -0,0 +1,70 @@ +// Copyright 2020-2022 Litentry Technologies GmbH. +// This file is part of Litentry. +// +// Litentry is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Litentry is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Litentry. If not, see . + +use itp_component_container::ComponentGetter; +use itp_sgx_crypto::Rsa3072Seal; +use itp_sgx_io::StaticSealedIO; +use lc_stf_task_receiver::{stf_task_receiver::run_stf_task_receiver, StfTaskContext}; +use log::*; +use sgx_types::sgx_status_t; +use std::sync::Arc; + +use crate::{ + error::{Error, Result}, + initialization::global_components::{ + EnclaveStfEnclaveSigner, GLOBAL_OCALL_API_COMPONENT, + GLOBAL_SHIELDING_KEY_REPOSITORY_COMPONENT, GLOBAL_STATE_OBSERVER_COMPONENT, + GLOBAL_TOP_POOL_AUTHOR_COMPONENT, + }, + GLOBAL_STATE_HANDLER_COMPONENT, +}; + +#[no_mangle] +pub unsafe extern "C" fn run_stf_task_handler() -> sgx_status_t { + if let Err(e) = run_stf_task_handler_internal() { + error!("Error while running stf task handler thread: {:?}", e); + return e.into() + } + + sgx_status_t::SGX_SUCCESS +} + +/// Internal [`run_stf_task_handler`] function to be able to use the `?` operator. +/// +/// Runs an extrinsic request inside the enclave, opening a channel and waiting for +/// senders to send requests. +fn run_stf_task_handler_internal() -> Result<()> { + let author_api = GLOBAL_TOP_POOL_AUTHOR_COMPONENT.get()?; + let state_handler = GLOBAL_STATE_HANDLER_COMPONENT.get()?; + let state_observer = GLOBAL_STATE_OBSERVER_COMPONENT.get()?; + + let shielding_key_repository = GLOBAL_SHIELDING_KEY_REPOSITORY_COMPONENT.get()?; + let shielding_key = Rsa3072Seal::unseal_from_static_file().unwrap(); + + let ocall_api = GLOBAL_OCALL_API_COMPONENT.get()?; + + let stf_enclave_signer = Arc::new(EnclaveStfEnclaveSigner::new( + state_observer, + ocall_api, + shielding_key_repository, + author_api.clone(), + )); + + let stf_task_context = + StfTaskContext::new(shielding_key, author_api, stf_enclave_signer, state_handler); + + run_stf_task_receiver(&stf_task_context).map_err(Error::StfTaskReceiver) +} diff --git a/tee-worker/enclave-runtime/src/sync.rs b/tee-worker/enclave-runtime/src/sync.rs new file mode 100644 index 0000000000..a348134d6f --- /dev/null +++ b/tee-worker/enclave-runtime/src/sync.rs @@ -0,0 +1,104 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +//! Primitives to handle multithreaded state access in the enclave. +//! +//! Note: In general the design should try to minimize usage of these, as potential deadlocks can +//! occur. Documentation of the `SgxRwLock` says that panics __might__ occur when trying to acquire +//! a lock multiple times in the same thread. However, tests have shown that it also might result in +//! a deadlock. +//! +//! @clangenb: Does currently not see any way to entirely get rid of these synchronization +//! primitives because we can only start new threads from the untrusted side. `parking_lot` would be +//! an alternative to consider for the primitives. It has several performance and ergonomic benefits +//! over the `std` lib's. One of the benefits would be compile-time deadlock detection (experimental). +//! Unfortunately, it would need to be ported to SGX. +//! +//! `https://amanieu.github.io/parking_lot/parking_lot/index.html` + +use crate::error::{Error, Result as EnclaveResult}; +use lazy_static::lazy_static; +use std::sync::{SgxRwLock, SgxRwLockReadGuard, SgxRwLockWriteGuard}; + +lazy_static! { + pub static ref SIDECHAIN_DB_LOCK: SgxRwLock<()> = Default::default(); +} + +pub struct EnclaveLock; + +impl SidechainRwLock for EnclaveLock { + fn read_sidechain_db() -> EnclaveResult> { + SIDECHAIN_DB_LOCK.read().map_err(|e| Error::Other(e.into())) + } + + fn write_sidechain_db() -> EnclaveResult> { + SIDECHAIN_DB_LOCK.write().map_err(|e| Error::Other(e.into())) + } +} + +pub trait SidechainRwLock { + fn read_sidechain_db() -> EnclaveResult>; + fn write_sidechain_db() -> EnclaveResult>; +} + +// simple type defs to prevent too long names +type AggregatedReadGuards<'a> = SgxRwLockReadGuard<'a, ()>; +type AggregatedWriteGuards<'a> = SgxRwLockWriteGuard<'a, ()>; + +/// Useful, if all state must be accessed. Reduces the number of lines. +pub trait EnclaveStateRWLock: SidechainRwLock { + /// return read locks of all enclave states + fn read_all() -> EnclaveResult>; + + /// return write locks of all enclave states + fn write_all() -> EnclaveResult>; +} + +impl EnclaveStateRWLock for T { + fn read_all() -> EnclaveResult> { + Self::read_sidechain_db() + } + + fn write_all() -> EnclaveResult> { + Self::write_sidechain_db() + } +} + +#[cfg(feature = "test")] +pub mod tests { + use super::*; + pub fn sidechain_rw_lock_works() { + drop(EnclaveLock::read_sidechain_db().unwrap()); + drop(EnclaveLock::write_sidechain_db().unwrap()); + + let x1 = EnclaveLock::read_sidechain_db().unwrap(); + let x2 = EnclaveLock::read_sidechain_db().unwrap(); + + drop((x1, x2)); + drop(EnclaveLock::write_sidechain_db().unwrap()) + } + + pub fn enclave_rw_lock_works() { + drop(EnclaveLock::read_all().unwrap()); + drop(EnclaveLock::write_all().unwrap()); + + let x1 = EnclaveLock::read_all().unwrap(); + let x2 = EnclaveLock::read_all().unwrap(); + + drop((x1, x2)); + drop(EnclaveLock::write_all().unwrap()) + } +} diff --git a/tee-worker/enclave-runtime/src/teeracle/mod.rs b/tee-worker/enclave-runtime/src/teeracle/mod.rs new file mode 100644 index 0000000000..cb60dd96f7 --- /dev/null +++ b/tee-worker/enclave-runtime/src/teeracle/mod.rs @@ -0,0 +1,156 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +use crate::{ + error::{Error, Result}, + initialization::global_components::GLOBAL_OCALL_API_COMPONENT, + utils::{ + get_extrinsic_factory_from_solo_or_parachain, + get_node_metadata_repository_from_solo_or_parachain, + }, +}; +use codec::{Decode, Encode}; +use core::slice; +use ita_exchange_oracle::{ + create_coin_gecko_oracle, create_coin_market_cap_oracle, + exchange_rate_oracle::{ExchangeRateOracle, OracleSource}, + metrics_exporter::ExportMetrics, + types::TradingPair, + GetExchangeRate, +}; +use itp_component_container::ComponentGetter; +use itp_extrinsics_factory::CreateExtrinsics; +use itp_node_api::metadata::{pallet_teeracle::TeeracleCallIndexes, provider::AccessNodeMetadata}; +use itp_types::OpaqueCall; +use itp_utils::write_slice_and_whitespace_pad; +use log::*; +use sgx_types::sgx_status_t; +use sp_runtime::OpaqueExtrinsic; +use std::{string::String, vec::Vec}; + +/// For now get the crypto/fiat currency exchange rate from coingecko and CoinMarketCap. +#[no_mangle] +pub unsafe extern "C" fn update_market_data_xt( + crypto_currency_ptr: *const u8, + crypto_currency_size: u32, + fiat_currency_ptr: *const u8, + fiat_currency_size: u32, + unchecked_extrinsic: *mut u8, + unchecked_extrinsic_size: u32, +) -> sgx_status_t { + let mut crypto_currency_slice = + slice::from_raw_parts(crypto_currency_ptr, crypto_currency_size as usize); + let crypto_currency: String = Decode::decode(&mut crypto_currency_slice).unwrap(); + + let mut fiat_currency_slice = + slice::from_raw_parts(fiat_currency_ptr, fiat_currency_size as usize); + let fiat_currency: String = Decode::decode(&mut fiat_currency_slice).unwrap(); + + let extrinsics = match update_market_data_internal(crypto_currency, fiat_currency) { + Ok(xts) => xts, + Err(e) => { + error!("Update market data failed: {:?}", e); + return sgx_status_t::SGX_ERROR_UNEXPECTED + }, + }; + + if extrinsics.is_empty() { + error!("Updating market data yielded no extrinsics"); + return sgx_status_t::SGX_ERROR_UNEXPECTED + } + let extrinsic_slice = + slice::from_raw_parts_mut(unchecked_extrinsic, unchecked_extrinsic_size as usize); + + // Save created extrinsic as slice in the return value unchecked_extrinsic. + if let Err(e) = write_slice_and_whitespace_pad(extrinsic_slice, extrinsics.encode()) { + error!("Copying encoded extrinsics into return slice failed: {:?}", e); + return sgx_status_t::SGX_ERROR_UNEXPECTED + } + + sgx_status_t::SGX_SUCCESS +} + +fn update_market_data_internal( + crypto_currency: String, + fiat_currency: String, +) -> Result> { + let extrinsics_factory = get_extrinsic_factory_from_solo_or_parachain()?; + let ocall_api = GLOBAL_OCALL_API_COMPONENT.get()?; + + let mut extrinsic_calls: Vec = Vec::new(); + + // Get the exchange rate + let trading_pair = TradingPair { crypto_currency, fiat_currency }; + + let coin_gecko_oracle = create_coin_gecko_oracle(ocall_api.clone()); + + match get_exchange_rate(trading_pair.clone(), coin_gecko_oracle) { + Ok(opaque_call) => extrinsic_calls.push(opaque_call), + Err(e) => { + error!("[-] Failed to get the newest exchange rate from CoinGecko. {:?}", e); + }, + }; + + let coin_market_cap_oracle = create_coin_market_cap_oracle(ocall_api); + match get_exchange_rate(trading_pair, coin_market_cap_oracle) { + Ok(oc) => extrinsic_calls.push(oc), + Err(e) => { + error!("[-] Failed to get the newest exchange rate from CoinMarketCap. {:?}", e); + }, + }; + + let extrinsics = extrinsics_factory.create_extrinsics(extrinsic_calls.as_slice(), None)?; + Ok(extrinsics) +} + +fn get_exchange_rate( + trading_pair: TradingPair, + oracle: ExchangeRateOracle, +) -> Result +where + OracleSourceType: OracleSource, + MetricsExporter: ExportMetrics, +{ + let (rate, base_url) = oracle + .get_exchange_rate(trading_pair.clone()) + .map_err(|e| Error::Other(e.into()))?; + + let source_base_url = base_url.as_str(); + + println!( + "Update the exchange rate: {} = {:?} for source {}", + trading_pair.clone().key(), + rate, + source_base_url, + ); + + let node_metadata_repository = get_node_metadata_repository_from_solo_or_parachain()?; + + let call_ids = node_metadata_repository + .get_from_metadata(|m| m.update_exchange_rate_call_indexes()) + .map_err(Error::NodeMetadataProvider)? + .map_err(|e| Error::Other(format!("{:?}", e).into()))?; + + let call = OpaqueCall::from_tuple(&( + call_ids, + source_base_url.as_bytes().to_vec(), + trading_pair.key().as_bytes().to_vec(), + Some(rate), + )); + + Ok(call) +} diff --git a/tee-worker/enclave-runtime/src/test/Counter.sol b/tee-worker/enclave-runtime/src/test/Counter.sol new file mode 100644 index 0000000000..ce3cce3259 --- /dev/null +++ b/tee-worker/enclave-runtime/src/test/Counter.sol @@ -0,0 +1,31 @@ +pragma solidity >=0.8.0; + +contract Counter { + uint256 private value; + address private last_caller; + + constructor() { + value = 1; + last_caller = msg.sender; + } + + fallback() external payable { value = 5; } + + function inc() public { + value += 1; + last_caller = msg.sender; + } + + function add(uint delta) public { + value += delta; + last_caller = msg.sender; + } + + function get_value() view public returns (uint) { + return value; + } + + function get_last_caller() view public returns (address) { + return last_caller; + } +} \ No newline at end of file diff --git a/tee-worker/enclave-runtime/src/test/cert_tests.rs b/tee-worker/enclave-runtime/src/test/cert_tests.rs new file mode 100644 index 0000000000..502efb093c --- /dev/null +++ b/tee-worker/enclave-runtime/src/test/cert_tests.rs @@ -0,0 +1,71 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + Copyright (C) 2017-2019 Baidu, Inc. All Rights Reserved. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ +use crate::test::mocks::attestation_ocall_mock::AttestationOCallMock; +use hex::FromHexError; +use itp_attestation_handler::cert::{verify_attn_report, verify_mra_cert}; +use sgx_types::{sgx_measurement_t, sgx_status_t, SGX_HASH_SIZE}; +use std::vec::Vec; + +// Test data and tests are mostly copied from: +// https://github.com/integritee-network/pallet-teerex/blob/master/ias-verify/ + +const TEST4_CERT: &[u8] = include_bytes!("fixtures/ra_dump_cert_TEST4.der"); + +const TEST4_MRENCLAVE: &str = "7a3454ec8f42e265cb5be7dfd111e1d95ac6076ed82a0948b2e2a45cf17b62a0"; + +const CERT_WRONG_PLATFORM_BLOB: &[u8] = b"0\x82\x0c\x8c0\x82\x0c2\xa0\x03\x02\x01\x02\x02\x01\x010\n\x06\x08*\x86H\xce=\x04\x03\x020\x121\x100\x0e\x06\x03U\x04\x03\x0c\x07MesaTEE0\x1e\x17\r190617124609Z\x17\r190915124609Z0\x121\x100\x0e\x06\x03U\x04\x03\x0c\x07MesaTEE0Y0\x13\x06\x07*\x86H\xce=\x02\x01\x06\x08*\x86H\xce=\x03\x01\x07\x03B\0\x04RT\x16\x16 \xef_\xd8\xe7\xc3\xb7\x03\x1d\xd6:\x1fF\xe3\xf2b!\xa9/\x8b\xd4\x82\x8f\xd1\xff[\x9c\x97\xbc\xf27\xb8,L\x8a\x01\xb0r;;\xa9\x83\xdc\x86\x9f\x1d%y\xf4;I\xe4Y\xc80'$K[\xd6\xa3\x82\x0bw0\x82\x0bs0\x82\x0bo\x06\t`\x86H\x01\x86\xf8B\x01\r\x04\x82\x0b`{\"id\":\"117077750682263877593646412006783680848\",\"timestamp\":\"2019-06-17T12:46:04.002066\",\"version\":3,\"isvEnclaveQuoteStatus\":\"GROUP_OUT_OF_DATE\",\"platformInfoBlob\":\"1602006504000900000909020401800000000000000000000008000009000000020000000000000B401A355B313FC939B4F48A54349C914A32A3AE2C4871BFABF22E960C55635869FC66293A3D9B2D58ED96CA620B65D669A444C80291314EF691E896F664317CF80C\",\"isvEnclaveQuoteBody\":\"AgAAAEALAAAIAAcAAAAAAOE6wgoHKsZsnVWSrsWX9kky0kWt9K4xcan0fQ996Ct+CAj//wGAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABwAAAAAAAAAHAAAAAAAAAFJJYIbPVot9NzRCjW2z9+k+9K8BsHQKzVMEHOR14hNbAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACD1xnnferKFHD2uvYqTXdDA8iZ22kCD5xw7h38CMfOngAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABSVBYWIO9f2OfDtwMd1jofRuPyYiGpL4vUgo/R/1ucl7zyN7gsTIoBsHI7O6mD3IafHSV59DtJ5FnIMCckS1vW\"}|EbPFH/ThUaS/dMZoDKC5EgmdUXUORFtQzF49Umi1P55oeESreJaUvmA0sg/ATSTn5t2e+e6ZoBQIUbLHjcWLMLzK4pJJUeHhok7EfVgoQ378i+eGR9v7ICNDGX7a1rroOe0s1OKxwo/0hid2KWvtAUBvf1BDkqlHy025IOiXWhXFLkb/qQwUZDWzrV4dooMfX5hfqJPi1q9s18SsdLPmhrGBheh9keazeCR9hiLhRO9TbnVgR9zJk43SPXW+pHkbNigW+2STpVAi5ugWaSwBOdK11ZjaEU1paVIpxQnlW1D6dj1Zc3LibMH+ly9ZGrbYtuJks4eRnjPhroPXxlJWpQ==|MIIEoTCCAwmgAwIBAgIJANEHdl0yo7CWMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJDQTEUMBIGA1UEBwwLU2FudGEgQ2xhcmExGjAYBgNVBAoMEUludGVsIENvcnBvcmF0aW9uMTAwLgYDVQQDDCdJbnRlbCBTR1ggQXR0ZXN0YXRpb24gUmVwb3J0IFNpZ25pbmcgQ0EwHhcNMTYxMTIyMDkzNjU4WhcNMjYxMTIwMDkzNjU4WjB7MQswCQYDVQQGEwJVUzELMAkGA1UECAwCQ0ExFDASBgNVBAcMC1NhbnRhIENsYXJhMRowGAYDVQQKDBFJbnRlbCBDb3Jwb3JhdGlvbjEtMCsGA1UEAwwkSW50ZWwgU0dYIEF0dGVzdGF0aW9uIFJlcG9ydCBTaWduaW5nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAqXot4OZuphR8nudFrAFiaGxxkgma/Es/BA+tbeCTUR106AL1ENcWA4FX3K+E9BBL0/7X5rj5nIgX/R/1ubhkKWw9gfqPG3KeAtIdcv/uTO1yXv50vqaPvE1CRChvzdS/ZEBqQ5oVvLTPZ3VEicQjlytKgN9cLnxbwtuvLUK7eyRPfJW/ksddOzP8VBBniolYnRCD2jrMRZ8nBM2ZWYwnXnwYeOAHV+W9tOhAImwRwKF/95yAsVwd21ryHMJBcGH70qLagZ7Ttyt++qO/6+KAXJuKwZqjRlEtSEz8gZQeFfVYgcwSfo96oSMAzVr7V0L6HSDLRnpb6xxmbPdqNol4tQIDAQABo4GkMIGhMB8GA1UdIwQYMBaAFHhDe3amfrzQr35CN+s1fDuHAVE8MA4GA1UdDwEB/wQEAwIGwDAMBgNVHRMBAf8EAjAAMGAGA1UdHwRZMFcwVaBToFGGT2h0dHA6Ly90cnVzdGVkc2VydmljZXMuaW50ZWwuY29tL2NvbnRlbnQvQ1JML1NHWC9BdHRlc3RhdGlvblJlcG9ydFNpZ25pbmdDQS5jcmwwDQYJKoZIhvcNAQELBQADggGBAGcIthtcK9IVRz4rRq+ZKE+7k50/OxUsmW8aavOzKb0iCx07YQ9rzi5nU73tME2yGRLzhSViFs/LpFa9lpQL6JL1aQwmDR74TxYGBAIi5f4I5TJoCCEqRHz91kpG6Uvyn2tLmnIdJbPE4vYvWLrtXXfFBSSPD4Afn7+3/XUggAlc7oCTizOfbbtOFlYA4g5KcYgS1J2ZAeMQqbUdZseZCcaZZZn65tdqee8UXZlDvx0+NdO0LR+5pFy+juM0wWbu59MvzcmTXbjsi7HY6zd53Yq5K244fwFHRQ8eOB0IWB+4PfM7FeAApZvlfqlKOlLcZL2uyVmzRkyR5yW72uo9mehX44CiPJ2fse9Y6eQtcfEhMPkmHXI01sN+KwPbpA39+xOsStjhP9N1Y1a2tQAVo+yVgLgV2Hws73Fc0o3wC78qPEA+v2aRs/Be3ZFDgDyghc/1fgU+7C+P6kbqd4poyb6IW8KCJbxfMJvkordNOgOUUxndPHEi/tb/U7uLjLOgPA==0\n\x06\x08*\x86H\xce=\x04\x03\x02\x03H\00E\x02!\0\xae6\x06\t@Sy\x8f\x8ec\x9d\xdci^Ex*\x92}\xdcG\x15A\x97\xd7\xd7\xd1\xccx\xe0\x1e\x08\x02 \x15Q\xa0BT\xde'~\xec\xbd\x027\xd3\xd8\x83\xf7\xe6Z\xc5H\xb4D\xf7\xe2\r\xa7\xe4^f\x10\x85p"; + +pub fn test_verify_mra_cert_should_work() { + let mr_enclave = get_mr_enclave_from_hex_string(TEST4_MRENCLAVE).unwrap(); + let attestation_ocall = + AttestationOCallMock::create_with_mr_enclave(sgx_measurement_t { m: mr_enclave }); + let result = verify_mra_cert(TEST4_CERT, &attestation_ocall); + + assert!(result.is_ok()); +} + +pub fn test_verify_wrong_cert_is_err() { + let mr_enclave = get_mr_enclave_from_hex_string(TEST4_MRENCLAVE).unwrap(); + let attestation_ocall = + AttestationOCallMock::create_with_mr_enclave(sgx_measurement_t { m: mr_enclave }); + let result = verify_mra_cert(CERT_WRONG_PLATFORM_BLOB, &attestation_ocall); + + assert!(result.is_err()); + assert_eq!(result.unwrap_err(), sgx_status_t::SGX_ERROR_UNEXPECTED); +} + +pub fn test_given_wrong_platform_info_when_verifying_attestation_report_then_return_error() { + let attestation_ocall = AttestationOCallMock::new(); + let result = verify_attn_report(CERT_WRONG_PLATFORM_BLOB, Vec::new(), &attestation_ocall); + + assert!(result.is_err()); + assert_eq!(result.unwrap_err(), sgx_status_t::SGX_ERROR_UNEXPECTED); +} + +fn get_mr_enclave_from_hex_string(input_str: &str) -> Result<[u8; SGX_HASH_SIZE], FromHexError> { + let decoded_str = hex::decode(input_str)?; + + if decoded_str.len() != SGX_HASH_SIZE { + return Err(FromHexError::InvalidStringLength) + } + + let mut mr_enclave = [0u8; SGX_HASH_SIZE]; + mr_enclave.clone_from_slice(decoded_str.as_slice()); + + Ok(mr_enclave) +} diff --git a/tee-worker/enclave-runtime/src/test/direct_rpc_tests.rs b/tee-worker/enclave-runtime/src/test/direct_rpc_tests.rs new file mode 100644 index 0000000000..2f2d8c54b4 --- /dev/null +++ b/tee-worker/enclave-runtime/src/test/direct_rpc_tests.rs @@ -0,0 +1,79 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + Copyright (C) 2017-2019 Baidu, Inc. All Rights Reserved. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +use crate::{rpc::worker_api_direct::public_api_rpc_handler, Hash}; +use codec::{Decode, Encode}; +use ita_stf::{Getter, TrustedGetter, TrustedGetterSigned}; +use itc_direct_rpc_server::{ + create_determine_watch, rpc_connection_registry::ConnectionRegistry, + rpc_ws_handler::RpcWsHandler, +}; +use itc_tls_websocket_server::{ConnectionToken, WebSocketMessageHandler}; +use itp_rpc::{RpcRequest, RpcReturnValue}; +use itp_stf_executor::{getter_executor::GetterExecutor, mocks::GetStateMock}; +use itp_stf_state_observer::mock::ObserveStateMock; +use itp_top_pool_author::mocks::AuthorApiMock; +use itp_types::{AccountId, DirectRequestStatus, Request, ShardIdentifier}; +use itp_utils::{FromHexPrefixed, ToHexPrefixed}; +use sp_core::ed25519::Signature; +use sp_runtime::MultiSignature; +use std::{string::ToString, sync::Arc, vec::Vec}; + +pub fn get_state_request_works() { + type TestState = u64; + + let connection_registry = Arc::new(ConnectionRegistry::::new()); + let watch_extractor = Arc::new(create_determine_watch::()); + + let state: TestState = 78234u64; + let state_observer = Arc::new(ObserveStateMock::::new(state)); + let getter_executor = + Arc::new(GetterExecutor::<_, GetStateMock>::new(state_observer)); + let top_pool_author = Arc::new(AuthorApiMock::default()); + let io_handler = public_api_rpc_handler(top_pool_author, getter_executor); + let rpc_handler = Arc::new(RpcWsHandler::new(io_handler, watch_extractor, connection_registry)); + + let getter = Getter::trusted(TrustedGetterSigned::new( + TrustedGetter::nonce(AccountId::new([0u8; 32])), + MultiSignature::Ed25519(Signature::from_raw([0u8; 64])), + )); + + let request = Request { shard: ShardIdentifier::default(), cyphertext: getter.encode() }; + + let request_string = + RpcRequest::compose_jsonrpc_call("state_executeGetter".to_string(), vec![request.to_hex()]) + .unwrap(); + + let response_string = + rpc_handler.handle_message(ConnectionToken(1), request_string).unwrap().unwrap(); + + assert!(!response_string.is_empty()); + + // Because we cannot de-serialize the RpcResponse here (unresolved serde_json and std/sgx feature issue), + // we hard-code the expected response. + //error!("{}", response_string); + //let response: RpcResponse = serde_json::from_str(&response_string).unwrap(); + + const EXPECTED_HEX_RETURN_VALUE: &str = "0x2801209a310100000000000000"; + assert!(response_string.contains(EXPECTED_HEX_RETURN_VALUE)); + let rpc_return_value = RpcReturnValue::from_hex(EXPECTED_HEX_RETURN_VALUE).unwrap(); + assert_eq!(rpc_return_value.status, DirectRequestStatus::Ok); + let decoded_value: Option> = + Option::decode(&mut rpc_return_value.value.as_slice()).unwrap(); + assert_eq!(decoded_value, Some(state.encode())); +} diff --git a/tee-worker/enclave-runtime/src/test/enclave_signer_tests.rs b/tee-worker/enclave-runtime/src/test/enclave_signer_tests.rs new file mode 100644 index 0000000000..afe67ad986 --- /dev/null +++ b/tee-worker/enclave-runtime/src/test/enclave_signer_tests.rs @@ -0,0 +1,75 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +use ita_sgx_runtime::Runtime; +use ita_stf::{AccountId, ShardIdentifier, Stf, TrustedCall}; +use itp_ocall_api::EnclaveAttestationOCallApi; +use itp_sgx_crypto::{ + ed25519_derivation::DeriveEd25519, key_repository::AccessKey, mocks::KeyRepositoryMock, +}; +use itp_sgx_externalities::SgxExternalities; +use itp_stf_executor::{enclave_signer::StfEnclaveSigner, traits::StfEnclaveSigning}; +use itp_stf_interface::{ + mocks::{CallExecutorMock, GetterExecutorMock}, + InitState, +}; +use itp_stf_state_observer::mock::ObserveStateMock; +use itp_test::mock::onchain_mock::OnchainMock; +use itp_top_pool_author::mocks::AuthorApiMock; +use sgx_crypto_helper::{rsa3072::Rsa3072KeyPair, RsaKeyPair}; +use sp_core::Pair; +use std::sync::Arc; + +type ShieldingKeyRepositoryMock = KeyRepositoryMock; +type TestStf = Stf; + +pub fn derive_key_is_deterministic() { + let rsa_key = Rsa3072KeyPair::new().unwrap(); + + let first_ed_key = rsa_key.derive_ed25519().unwrap(); + let second_ed_key = rsa_key.derive_ed25519().unwrap(); + assert_eq!(first_ed_key.public(), second_ed_key.public()); +} + +pub fn enclave_signer_signatures_are_valid() { + let top_pool_author = Arc::new(AuthorApiMock::default()); + let ocall_api = Arc::new(OnchainMock::default()); + let shielding_key_repo = Arc::new(ShieldingKeyRepositoryMock::default()); + let enclave_account: AccountId = shielding_key_repo + .retrieve_key() + .unwrap() + .derive_ed25519() + .unwrap() + .public() + .into(); + + let state_observer: Arc> = + Arc::new(ObserveStateMock::new(TestStf::init_state(enclave_account.clone()))); + let shard = ShardIdentifier::default(); + let mr_enclave = ocall_api.get_mrenclave_of_self().unwrap(); + let enclave_signer = StfEnclaveSigner::<_, _, _, TestStf, _>::new( + state_observer, + ocall_api, + shielding_key_repo, + top_pool_author, + ); + let trusted_call = + TrustedCall::balance_shield(enclave_account, AccountId::new([3u8; 32]), 200u128); + + let trusted_call_signed = enclave_signer.sign_call_with_self(&trusted_call, &shard).unwrap(); + assert!(trusted_call_signed.verify_signature(&mr_enclave.m, &shard)); +} diff --git a/tee-worker/enclave-runtime/src/test/evm_pallet_tests.rs b/tee-worker/enclave-runtime/src/test/evm_pallet_tests.rs new file mode 100644 index 0000000000..f14987785e --- /dev/null +++ b/tee-worker/enclave-runtime/src/test/evm_pallet_tests.rs @@ -0,0 +1,367 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +use crate::test::fixtures::test_setup::{test_setup, TestStf}; +use core::str::FromStr; +use ita_sgx_runtime::{AddressMapping, HashedAddressMapping, Index, System}; +use ita_stf::{ + evm_helpers::{ + create_code_hash, evm_create2_address, evm_create_address, get_evm_account_codes, + get_evm_account_storages, + }, + test_genesis::{endow, endowed_account as funded_pair}, + KeyPair, State, TrustedCall, +}; +use itp_node_api::metadata::{metadata_mocks::NodeMetadataMock, provider::NodeMetadataRepository}; +use itp_sgx_externalities::SgxExternalitiesTrait; +use itp_stf_interface::StateCallInterface; +use itp_types::{AccountId, OpaqueCall, ShardIdentifier}; +use primitive_types::H256; +use sp_core::{crypto::Pair, H160, U256}; +use std::{string::ToString, sync::Arc, vec::Vec}; +use substrate_api_client::utils::FromHexString; + +pub fn test_evm_call() { + // given + let (_, mut state, shard, mrenclave, ..) = test_setup(); + let mut opaque_vec = Vec::new(); + + // Create the sender account. + let sender = funded_pair(); + let sender_acc: AccountId = sender.public().into(); + let mut sender_evm_acc_slice: [u8; 20] = [0; 20]; + sender_evm_acc_slice + .copy_from_slice((<[u8; 32]>::from(sender_acc.clone())).get(0..20).unwrap()); + let sender_evm_acc: H160 = sender_evm_acc_slice.into(); + // Ensure the substrate version of the evm account has some money. + let sender_evm_substrate_addr = + ita_sgx_runtime::HashedAddressMapping::into_account_id(sender_evm_acc); + endow(&mut state, vec![(sender_evm_substrate_addr, 51_777_000_000_000, 0)]); + + // Create the receiver account. + let destination_evm_acc = H160::from_str("1000000000000000000000000000000000000001").unwrap(); + let destination_evm_substrate_addr = + ita_sgx_runtime::HashedAddressMapping::into_account_id(destination_evm_acc); + assert_eq!( + state.execute_with(|| System::account(&destination_evm_substrate_addr).data.free), + 0 + ); + + let transfer_value: u128 = 1_000_000_000; + + let trusted_call = TrustedCall::evm_call( + sender_acc, + sender_evm_acc, + destination_evm_acc, + Vec::new(), + U256::from(transfer_value), + 21776, // gas limit + U256::from(1_000_000_000), + None, + Some(U256::from(0)), + Vec::new(), + ) + .sign(&sender.clone().into(), 0, &mrenclave, &shard); + + // when + let repo = Arc::new(NodeMetadataRepository::::default()); + let shard = ShardIdentifier::default(); + TestStf::execute_call(&mut state, &shard, trusted_call, &mut opaque_vec, repo).unwrap(); + + // then + assert_eq!( + transfer_value, + state.execute_with(|| System::account(&destination_evm_substrate_addr).data.free) + ); +} + +pub fn test_evm_counter() { + // given + let (_, mut state, shard, mrenclave, ..) = test_setup(); + let mut opaque_vec = Vec::new(); + + // Create the sender account. + let sender = funded_pair(); + let sender_acc: AccountId = sender.public().into(); + let mut sender_evm_acc_slice: [u8; 20] = [0; 20]; + sender_evm_acc_slice + .copy_from_slice((<[u8; 32]>::from(sender_acc.clone())).get(0..20).unwrap()); + let sender_evm_acc: H160 = sender_evm_acc_slice.into(); + // Ensure the substrate version of the evm account has some money. + let sender_evm_substrate_addr = + ita_sgx_runtime::HashedAddressMapping::into_account_id(sender_evm_acc); + endow(&mut state, vec![(sender_evm_substrate_addr, 51_777_000_000_000, 0)]); + + // Smart Contract from Counter.sol. + let smart_contract = "608060405234801561001057600080fd5b50600160008190555033600160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550610377806100696000396000f3fe6080604052600436106100435760003560e01c80631003e2d21461004d57806333cf508014610076578063371303c0146100a157806358992216146100b857610044565b5b60056000819055005b34801561005957600080fd5b50610074600480360381019061006f9190610209565b6100e3565b005b34801561008257600080fd5b5061008b61013f565b6040516100989190610245565b60405180910390f35b3480156100ad57600080fd5b506100b6610148565b005b3480156100c457600080fd5b506100cd6101a4565b6040516100da91906102a1565b60405180910390f35b806000808282546100f491906102eb565b9250508190555033600160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555050565b60008054905090565b600160008082825461015a91906102eb565b9250508190555033600160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550565b6000600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16905090565b600080fd5b6000819050919050565b6101e6816101d3565b81146101f157600080fd5b50565b600081359050610203816101dd565b92915050565b60006020828403121561021f5761021e6101ce565b5b600061022d848285016101f4565b91505092915050565b61023f816101d3565b82525050565b600060208201905061025a6000830184610236565b92915050565b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b600061028b82610260565b9050919050565b61029b81610280565b82525050565b60006020820190506102b66000830184610292565b92915050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b60006102f6826101d3565b9150610301836101d3565b9250827fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff03821115610336576103356102bc565b5b82820190509291505056fea2646970667358221220b37e993e133ed19c840809cc8acbbba8116dee3744ba01c81044d75146805c9364736f6c634300080f0033"; + + let trusted_call = TrustedCall::evm_create( + sender_acc.clone(), + sender_evm_acc, + Vec::from_hex(smart_contract.to_string()).unwrap(), + U256::from(0), + 10_000_000, // gas limit + U256::from(1), // max_fee_per_gas !>= min_gas_price defined in runtime + None, + Some(U256::from(0)), + Vec::new(), + ) + .sign(&sender.clone().into(), 0, &mrenclave, &shard); + + // when + let execution_address = evm_create_address(sender_evm_acc, 0); + let repo = Arc::new(NodeMetadataRepository::::default()); + let shard = ShardIdentifier::default(); + TestStf::execute_call(&mut state, &shard, trusted_call, &mut opaque_vec, repo).unwrap(); + + // then + assert_eq!( + execution_address, + H160::from_slice( + &Vec::from_hex("0xce2c9e7f9c10049996173b2ca2d9a6815a70e890".to_string()).unwrap(), + ) + ); + + assert!(state.execute_with(|| get_evm_account_codes(&execution_address).is_some())); + + let counter_value = state + .execute_with(|| get_evm_account_storages(&execution_address, &H256::zero())) + .unwrap(); + assert_eq!(H256::from_low_u64_be(1), counter_value); + let last_caller = state + .execute_with(|| get_evm_account_storages(&execution_address, &H256::from_low_u64_be(1))) + .unwrap(); + assert_eq!(H256::from(sender_evm_acc), last_caller); + + // Call to inc() function + // in solidity compile information you get the hash of the call + let inc_function_input = Vec::from_hex("371303c0".to_string()).unwrap(); + + execute_and_verify_evm_call( + sender_acc.clone(), + sender_evm_acc, + execution_address, + inc_function_input.clone(), + 1, + 1, + sender.clone().into(), + &mrenclave, + &shard, + &mut state, + &mut opaque_vec, + 2, + ); + + // Call the fallback function + execute_and_verify_evm_call( + sender_acc.clone(), + sender_evm_acc, + execution_address, + Vec::new(), // Empty input calls the fallback function. + 2, + 2, + sender.clone().into(), + &mrenclave, + &shard, + &mut state, + &mut opaque_vec, + 5, + ); + + // Call to inc() function + // in solidity compile information you get the hash of the call + execute_and_verify_evm_call( + sender_acc.clone(), + sender_evm_acc, + execution_address, + inc_function_input, + 3, + 3, + sender.clone().into(), + &mrenclave, + &shard, + &mut state, + &mut opaque_vec, + 6, + ); + + // Call to add() function + // in solidity compile information you get the hash of the call + let function_hash = "1003e2d2"; + // 32 byte string of the value to add in hex + let add_value = "0000000000000000000000000000000000000000000000000000000000000002"; + let add_function_input = Vec::from_hex(format!("{}{}", function_hash, add_value)).unwrap(); + + execute_and_verify_evm_call( + sender_acc.clone(), + sender_evm_acc, + execution_address, + add_function_input, + 4, + 4, + sender.clone().into(), + &mrenclave, + &shard, + &mut state, + &mut opaque_vec, + 8, + ); +} + +fn execute_and_verify_evm_call( + sender_acc: AccountId, + sender_evm_acc: H160, + execution_address: H160, + function_input: Vec, + evm_nonce: i8, + nonce: Index, + pair: KeyPair, + mrenclave: &[u8; 32], + shard: &ShardIdentifier, + state: &mut State, + calls: &mut Vec, + counter_expected: u64, +) { + let inc_call = TrustedCall::evm_call( + sender_acc, + sender_evm_acc, + execution_address, + function_input, + U256::from(0), + 10_000_000, // gas limit + U256::from(1), // max_fee_per_gas !>= min_gas_price defined in runtime + None, + Some(U256::from(evm_nonce)), + Vec::new(), + ) + .sign(&pair, nonce, &mrenclave, &shard); + let repo = Arc::new(NodeMetadataRepository::::default()); + let shard = ShardIdentifier::default(); + TestStf::execute_call(state, &shard, inc_call, calls, repo).unwrap(); + + let counter_value = state + .execute_with(|| get_evm_account_storages(&execution_address, &H256::zero())) + .unwrap(); + assert_eq!(counter_value, H256::from_low_u64_be(counter_expected)); +} + +pub fn test_evm_create() { + // given + let (_, mut state, shard, mrenclave, ..) = test_setup(); + let mut opaque_vec = Vec::new(); + + // Create the sender account. + let sender = funded_pair(); + let sender_acc: AccountId = sender.public().into(); + let mut sender_evm_acc_slice: [u8; 20] = [0; 20]; + sender_evm_acc_slice + .copy_from_slice((<[u8; 32]>::from(sender_acc.clone())).get(0..20).unwrap()); + let sender_evm_acc: H160 = sender_evm_acc_slice.into(); + // Ensure the substrate version of the evm account has some money. + let sender_evm_substrate_addr = HashedAddressMapping::into_account_id(sender_evm_acc); + endow(&mut state, vec![(sender_evm_substrate_addr.clone(), 51_777_000_000_000, 0)]); + + // Bytecode from Counter.sol + let smart_contract = "608060405234801561001057600080fd5b50600160008190555033600160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550610377806100696000396000f3fe6080604052600436106100435760003560e01c80631003e2d21461004d57806333cf508014610076578063371303c0146100a157806358992216146100b857610044565b5b60056000819055005b34801561005957600080fd5b50610074600480360381019061006f9190610209565b6100e3565b005b34801561008257600080fd5b5061008b61013f565b6040516100989190610245565b60405180910390f35b3480156100ad57600080fd5b506100b6610148565b005b3480156100c457600080fd5b506100cd6101a4565b6040516100da91906102a1565b60405180910390f35b806000808282546100f491906102eb565b9250508190555033600160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555050565b60008054905090565b600160008082825461015a91906102eb565b9250508190555033600160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550565b6000600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16905090565b600080fd5b6000819050919050565b6101e6816101d3565b81146101f157600080fd5b50565b600081359050610203816101dd565b92915050565b60006020828403121561021f5761021e6101ce565b5b600061022d848285016101f4565b91505092915050565b61023f816101d3565b82525050565b600060208201905061025a6000830184610236565b92915050565b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b600061028b82610260565b9050919050565b61029b81610280565b82525050565b60006020820190506102b66000830184610292565b92915050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b60006102f6826101d3565b9150610301836101d3565b9250827fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff03821115610336576103356102bc565b5b82820190509291505056fea2646970667358221220b37e993e133ed19c840809cc8acbbba8116dee3744ba01c81044d75146805c9364736f6c634300080f0033"; + let smart_contract = Vec::from_hex(smart_contract.to_string()).unwrap(); + + let trusted_call = TrustedCall::evm_create( + sender_acc.clone(), + sender_evm_acc, + smart_contract.clone(), + U256::from(0), // value + 10_000_000, // gas limit + U256::from(1), // max_fee_per_gas !>= min_gas_price defined in runtime + None, + Some(U256::from(0)), + Vec::new(), + ) + .sign(&sender.clone().into(), 0, &mrenclave, &shard); + + // Should be the first call of the evm account + let nonce = state.execute_with(|| System::account_nonce(&sender_evm_substrate_addr)); + assert_eq!(nonce, 0); + let execution_address = evm_create_address(sender_evm_acc, nonce); + let repo = Arc::new(NodeMetadataRepository::::default()); + let shard = ShardIdentifier::default(); + TestStf::execute_call(&mut state, &shard, trusted_call, &mut opaque_vec, repo).unwrap(); + + assert_eq!( + execution_address, + H160::from_slice( + &Vec::from_hex("0xce2c9e7f9c10049996173b2ca2d9a6815a70e890".to_string()).unwrap(), + ) + ); + assert!(state.execute_with(|| get_evm_account_codes(&execution_address).is_some())); + + // Ensure the nonce of the evm account has been increased by one + // Should be the first call of the evm account + let nonce = state.execute_with(|| System::account_nonce(&sender_evm_substrate_addr)); + assert_eq!(nonce, 1); +} + +pub fn test_evm_create2() { + // given + let (_, mut state, shard, mrenclave, ..) = test_setup(); + let mut opaque_vec = Vec::new(); + + // Create the sender account. + let sender = funded_pair(); + let sender_acc: AccountId = sender.public().into(); + let mut sender_evm_acc_slice: [u8; 20] = [0; 20]; + sender_evm_acc_slice + .copy_from_slice((<[u8; 32]>::from(sender_acc.clone())).get(0..20).unwrap()); + let sender_evm_acc: H160 = sender_evm_acc_slice.into(); + // Ensure the substrate version of the evm account has some money. + let sender_evm_substrate_addr = HashedAddressMapping::into_account_id(sender_evm_acc); + endow(&mut state, vec![(sender_evm_substrate_addr, 51_777_000_000_000, 0)]); + + let salt = H256::from_low_u64_be(20); + // Bytecode from Counter.sol + let smart_contract = "608060405234801561001057600080fd5b50600160008190555033600160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550610377806100696000396000f3fe6080604052600436106100435760003560e01c80631003e2d21461004d57806333cf508014610076578063371303c0146100a157806358992216146100b857610044565b5b60056000819055005b34801561005957600080fd5b50610074600480360381019061006f9190610209565b6100e3565b005b34801561008257600080fd5b5061008b61013f565b6040516100989190610245565b60405180910390f35b3480156100ad57600080fd5b506100b6610148565b005b3480156100c457600080fd5b506100cd6101a4565b6040516100da91906102a1565b60405180910390f35b806000808282546100f491906102eb565b9250508190555033600160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555050565b60008054905090565b600160008082825461015a91906102eb565b9250508190555033600160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550565b6000600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16905090565b600080fd5b6000819050919050565b6101e6816101d3565b81146101f157600080fd5b50565b600081359050610203816101dd565b92915050565b60006020828403121561021f5761021e6101ce565b5b600061022d848285016101f4565b91505092915050565b61023f816101d3565b82525050565b600060208201905061025a6000830184610236565b92915050565b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b600061028b82610260565b9050919050565b61029b81610280565b82525050565b60006020820190506102b66000830184610292565b92915050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b60006102f6826101d3565b9150610301836101d3565b9250827fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff03821115610336576103356102bc565b5b82820190509291505056fea2646970667358221220b37e993e133ed19c840809cc8acbbba8116dee3744ba01c81044d75146805c9364736f6c634300080f0033"; + let smart_contract = Vec::from_hex(smart_contract.to_string()).unwrap(); + + let trusted_call = TrustedCall::evm_create2( + sender_acc.clone(), + sender_evm_acc, + smart_contract.clone(), + salt, + U256::from(0), // value + 10_000_000, // gas limit + U256::from(1), // max_fee_per_gas !>= min_gas_price defined in runtime + None, + Some(U256::from(0)), + Vec::new(), + ) + .sign(&sender.clone().into(), 0, &mrenclave, &shard); + + // when + let code_hash = create_code_hash(&smart_contract); + let execution_address = evm_create2_address(sender_evm_acc, salt, code_hash); + let repo = Arc::new(NodeMetadataRepository::::default()); + let shard = ShardIdentifier::default(); + TestStf::execute_call(&mut state, &shard, trusted_call, &mut opaque_vec, repo).unwrap(); + + // then + assert_eq!( + execution_address, + H160::from_slice( + &Vec::from_hex("0xe07ad7925f6b2b10c5a7653fb16db7a984059d11".to_string()).unwrap(), + ) + ); + + assert!(state.execute_with(|| get_evm_account_codes(&execution_address).is_some())); +} diff --git a/tee-worker/enclave-runtime/src/test/fixtures/components.rs b/tee-worker/enclave-runtime/src/test/fixtures/components.rs new file mode 100644 index 0000000000..896abd3616 --- /dev/null +++ b/tee-worker/enclave-runtime/src/test/fixtures/components.rs @@ -0,0 +1,66 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + Copyright (C) 2017-2019 Baidu, Inc. All Rights Reserved. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +use crate::test::mocks::types::{TestOCallApi, TestRpcResponder, TestSigner, TestTopPool}; +use codec::Encode; +use ita_stf::{KeyPair, TrustedCall, TrustedCallSigned, TrustedOperation}; +use itp_ocall_api::EnclaveAttestationOCallApi; +use itp_sgx_crypto::ShieldingCryptoEncrypt; +use itp_top_pool::pool::Options as PoolOptions; +use itp_top_pool_author::api::SidechainApi; +use itp_types::{Block as ParentchainBlock, Enclave, ShardIdentifier}; +use sp_core::{ed25519, Pair, H256}; +use sp_runtime::traits::Header as HeaderTrait; +use std::{boxed::Box, sync::Arc, vec::Vec}; + +pub(crate) fn create_top_pool() -> Arc { + let rpc_responder = Arc::new(TestRpcResponder::new()); + let sidechain_api = Arc::new(SidechainApi::::new()); + Arc::new(TestTopPool::create(PoolOptions::default(), sidechain_api, rpc_responder)) +} + +pub(crate) fn create_ocall_api>( + header: &Header, + signer: &TestSigner, +) -> Arc { + let enclave_validateer = Enclave::new( + signer.public().into(), + Default::default(), + Default::default(), + Default::default(), + ); + Arc::new(TestOCallApi::default().add_validateer_set(header, Some(vec![enclave_validateer]))) +} + +pub(crate) fn encrypt_trusted_operation( + shielding_key: &ShieldingKey, + trusted_operation: &TrustedOperation, +) -> Vec { + let encoded_operation = trusted_operation.encode(); + shielding_key.encrypt(encoded_operation.as_slice()).unwrap() +} + +pub(crate) fn sign_trusted_call( + trusted_call: &TrustedCall, + attestation_api: &AttestationApi, + shard_id: &ShardIdentifier, + from: ed25519::Pair, +) -> TrustedCallSigned { + let mr_enclave = attestation_api.get_mrenclave_of_self().unwrap(); + trusted_call.sign(&KeyPair::Ed25519(Box::new(from)), 0, &mr_enclave.m, shard_id) +} diff --git a/tee-worker/enclave-runtime/src/test/fixtures/initialize_test_state.rs b/tee-worker/enclave-runtime/src/test/fixtures/initialize_test_state.rs new file mode 100644 index 0000000000..33148c424c --- /dev/null +++ b/tee-worker/enclave-runtime/src/test/fixtures/initialize_test_state.rs @@ -0,0 +1,41 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + Copyright (C) 2017-2019 Baidu, Inc. All Rights Reserved. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +use super::test_setup::TestStf; +use ita_stf::{AccountId, State}; +use itp_sgx_externalities::{SgxExternalities, SgxExternalitiesTrait}; +use itp_stf_interface::InitState; +use itp_stf_state_handler::handle_state::HandleState; +use itp_types::ShardIdentifier; + +/// Returns an empty `State` with the corresponding `ShardIdentifier`. +pub fn init_state>( + state_handler: &S, + enclave_account: AccountId, +) -> (State, ShardIdentifier) { + let shard = ShardIdentifier::default(); + + let _hash = state_handler.initialize_shard(shard).unwrap(); + let (lock, _) = state_handler.load_for_mutation(&shard).unwrap(); + let mut state = TestStf::init_state(enclave_account); + state.prune_state_diff(); + + state_handler.write_after_mutation(state.clone(), lock, &shard).unwrap(); + + (state, shard) +} diff --git a/tee-worker/enclave-runtime/src/test/fixtures/mod.rs b/tee-worker/enclave-runtime/src/test/fixtures/mod.rs new file mode 100644 index 0000000000..bc01106db1 --- /dev/null +++ b/tee-worker/enclave-runtime/src/test/fixtures/mod.rs @@ -0,0 +1,21 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + Copyright (C) 2017-2019 Baidu, Inc. All Rights Reserved. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +pub mod components; +pub mod initialize_test_state; +pub mod test_setup; diff --git a/tee-worker/enclave-runtime/src/test/fixtures/ra_dump_cert_TEST4.der b/tee-worker/enclave-runtime/src/test/fixtures/ra_dump_cert_TEST4.der new file mode 100644 index 0000000000..2e775236d6 Binary files /dev/null and b/tee-worker/enclave-runtime/src/test/fixtures/ra_dump_cert_TEST4.der differ diff --git a/tee-worker/enclave-runtime/src/test/fixtures/test_ra_signer_attn_MRSIGNER1_MRENCLAVE1.bin b/tee-worker/enclave-runtime/src/test/fixtures/test_ra_signer_attn_MRSIGNER1_MRENCLAVE1.bin new file mode 100644 index 0000000000..d7149d37d5 Binary files /dev/null and b/tee-worker/enclave-runtime/src/test/fixtures/test_ra_signer_attn_MRSIGNER1_MRENCLAVE1.bin differ diff --git a/tee-worker/enclave-runtime/src/test/fixtures/test_setup.rs b/tee-worker/enclave-runtime/src/test/fixtures/test_setup.rs new file mode 100644 index 0000000000..c8be6c63a2 --- /dev/null +++ b/tee-worker/enclave-runtime/src/test/fixtures/test_setup.rs @@ -0,0 +1,109 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +use crate::{ + ocall::OcallApi, + test::{ + fixtures::initialize_test_state::init_state, mocks::rpc_responder_mock::RpcResponderMock, + }, +}; +use ita_sgx_runtime::Runtime; +use ita_stf::{Getter, ShardIdentifier, State, Stf, TrustedCallSigned}; +use itp_node_api::metadata::{metadata_mocks::NodeMetadataMock, provider::NodeMetadataRepository}; +use itp_ocall_api::EnclaveAttestationOCallApi; +use itp_sgx_crypto::{ed25519_derivation::DeriveEd25519, mocks::KeyRepositoryMock}; +use itp_sgx_externalities::SgxExternalities; +use itp_stf_executor::executor::StfExecutor; +use itp_test::mock::{ + handle_state_mock::HandleStateMock, metrics_ocall_mock::MetricsOCallMock, + shielding_crypto_mock::ShieldingCryptoMock, +}; +use itp_top_pool::{basic_pool::BasicPool, pool::ExtrinsicHash}; +use itp_top_pool_author::{api::SidechainApi, author::Author, top_filter::AllowAllTopsFilter}; +use itp_types::{Block, MrEnclave}; +use sp_core::{crypto::Pair, ed25519 as spEd25519}; +use std::sync::Arc; + +pub type TestRpcResponder = RpcResponderMock>>; +pub type TestTopPool = BasicPool, Block, TestRpcResponder>; +pub type TestShieldingKeyRepo = KeyRepositoryMock; +pub type TestTopPoolAuthor = Author< + TestTopPool, + AllowAllTopsFilter, + HandleStateMock, + TestShieldingKeyRepo, + MetricsOCallMock, +>; +pub type TestStf = Stf; + +pub type TestStfExecutor = + StfExecutor, TestStf>; + +/// Returns all the things that are commonly used in tests and runs +/// `ensure_no_empty_shard_directory_exists` +pub fn test_setup() -> ( + Arc, + State, + ShardIdentifier, + MrEnclave, + ShieldingCryptoMock, + Arc, + Arc, +) { + let shielding_key = ShieldingCryptoMock::default(); + let shielding_key_repo = Arc::new(KeyRepositoryMock::new(shielding_key.clone())); + + let state_handler = Arc::new(HandleStateMock::default()); + let (state, shard) = + init_state(state_handler.as_ref(), enclave_call_signer(&shielding_key).public().into()); + let top_pool = test_top_pool(); + let mrenclave = OcallApi.get_mrenclave_of_self().unwrap().m; + + let node_metadata_repo = Arc::new(NodeMetadataRepository::new(NodeMetadataMock::new())); + let stf_executor = Arc::new(TestStfExecutor::new( + Arc::new(OcallApi), + state_handler.clone(), + node_metadata_repo, + )); + + ( + Arc::new(TestTopPoolAuthor::new( + Arc::new(top_pool), + AllowAllTopsFilter, + state_handler.clone(), + shielding_key_repo, + Arc::new(MetricsOCallMock::default()), + )), + state, + shard, + mrenclave, + shielding_key, + state_handler, + stf_executor, + ) +} + +pub fn test_top_pool() -> TestTopPool { + let chain_api = Arc::new(SidechainApi::::new()); + let top_pool = + BasicPool::create(Default::default(), chain_api, Arc::new(TestRpcResponder::new())); + + top_pool +} + +pub fn enclave_call_signer(key_source: &Source) -> spEd25519::Pair { + key_source.derive_ed25519().unwrap() +} diff --git a/tee-worker/enclave-runtime/src/test/ipfs_tests.rs b/tee-worker/enclave-runtime/src/test/ipfs_tests.rs new file mode 100644 index 0000000000..27b187a047 --- /dev/null +++ b/tee-worker/enclave-runtime/src/test/ipfs_tests.rs @@ -0,0 +1,42 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + Copyright (C) 2017-2019 Baidu, Inc. All Rights Reserved. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +use crate::{ipfs::IpfsContent, ocall::OcallApi}; +use itp_ocall_api::EnclaveIpfsOCallApi; +use log::*; +use std::{fs::File, io::Read, vec::Vec}; + +#[allow(unused)] +fn test_ocall_read_write_ipfs() { + info!("testing IPFS read/write. Hopefully ipfs daemon is running..."); + let enc_state: Vec = vec![20; 4 * 512 * 1024]; + + let cid = OcallApi.write_ipfs(enc_state.as_slice()).unwrap(); + + OcallApi.read_ipfs(&cid).unwrap(); + + let cid_str = std::str::from_utf8(&cid.0).unwrap(); + let mut f = File::open(&cid_str).unwrap(); + let mut content_buf = Vec::new(); + f.read_to_end(&mut content_buf).unwrap(); + info!("reading file {:?} of size {} bytes", f, &content_buf.len()); + + let mut ipfs_content = IpfsContent::new(cid_str, content_buf); + let verification = ipfs_content.verify(); + assert!(verification.is_ok()); +} diff --git a/tee-worker/enclave-runtime/src/test/mocks/attestation_ocall_mock.rs b/tee-worker/enclave-runtime/src/test/mocks/attestation_ocall_mock.rs new file mode 100644 index 0000000000..af94effda5 --- /dev/null +++ b/tee-worker/enclave-runtime/src/test/mocks/attestation_ocall_mock.rs @@ -0,0 +1,86 @@ +/* + CCopyright 2021 Integritee AG and Supercomputing Systems AG + Copyright (C) 2017-2019 Baidu, Inc. All Rights Reserved. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +use itp_ocall_api::EnclaveAttestationOCallApi; +use sgx_types::*; +use std::{ + fmt::{Debug, Formatter, Result as FormatResult}, + vec::Vec, +}; + +#[derive(Clone)] +pub struct AttestationOCallMock { + mr_enclave: sgx_measurement_t, +} + +impl AttestationOCallMock { + pub fn new() -> Self { + Default::default() + } + + pub fn create_with_mr_enclave(mr_enclave: sgx_measurement_t) -> Self { + AttestationOCallMock { mr_enclave } + } +} + +impl EnclaveAttestationOCallApi for AttestationOCallMock { + fn sgx_init_quote(&self) -> SgxResult<(sgx_target_info_t, sgx_epid_group_id_t)> { + unreachable!() + } + + fn get_ias_socket(&self) -> SgxResult { + unreachable!() + } + + fn get_quote( + &self, + _sig_rl: Vec, + _report: sgx_report_t, + _sign_type: sgx_quote_sign_type_t, + _spid: sgx_spid_t, + _quote_nonce: sgx_quote_nonce_t, + ) -> SgxResult<(sgx_report_t, Vec)> { + unreachable!() + } + + fn get_update_info( + &self, + _platform_info: sgx_platform_info_t, + _enclave_trusted: i32, + ) -> SgxResult { + Ok(sgx_update_info_bit_t { csmeFwUpdate: 0, pswUpdate: 0, ucodeUpdate: 0 }) + } + + fn get_mrenclave_of_self(&self) -> SgxResult { + Ok(self.mr_enclave) + } +} + +impl Default for AttestationOCallMock { + fn default() -> Self { + AttestationOCallMock { mr_enclave: sgx_measurement_t { m: [1; SGX_HASH_SIZE] } } + } +} + +impl Debug for AttestationOCallMock { + fn fmt(&self, f: &mut Formatter<'_>) -> FormatResult { + f.debug_struct("AttestationOCallMock") + .field("mr_enclave", &self.mr_enclave.m) + .finish() + } +} diff --git a/tee-worker/enclave-runtime/src/test/mocks/enclave_rpc_ocall_mock.rs b/tee-worker/enclave-runtime/src/test/mocks/enclave_rpc_ocall_mock.rs new file mode 100644 index 0000000000..23003989e8 --- /dev/null +++ b/tee-worker/enclave-runtime/src/test/mocks/enclave_rpc_ocall_mock.rs @@ -0,0 +1,40 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + Copyright (C) 2017-2019 Baidu, Inc. All Rights Reserved. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +use codec::Encode; +use itp_ocall_api::EnclaveRpcOCallApi; +use itp_types::TrustedOperationStatus; +use sgx_types::SgxResult; +use std::vec::Vec; + +#[derive(Clone, Debug, Default)] +pub struct EnclaveRpcOCallMock; + +impl EnclaveRpcOCallApi for EnclaveRpcOCallMock { + fn update_status_event( + &self, + _hash: H, + _status_update: TrustedOperationStatus, + ) -> SgxResult<()> { + Ok(()) + } + + fn send_state(&self, _hash: H, _value_opt: Option>) -> SgxResult<()> { + Ok(()) + } +} diff --git a/tee-worker/enclave-runtime/src/test/mocks/mod.rs b/tee-worker/enclave-runtime/src/test/mocks/mod.rs new file mode 100644 index 0000000000..f663d6ae9f --- /dev/null +++ b/tee-worker/enclave-runtime/src/test/mocks/mod.rs @@ -0,0 +1,23 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + Copyright (C) 2017-2019 Baidu, Inc. All Rights Reserved. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +pub mod attestation_ocall_mock; +pub mod enclave_rpc_ocall_mock; +pub mod propose_to_import_call_mock; +pub mod rpc_responder_mock; +pub mod types; diff --git a/tee-worker/enclave-runtime/src/test/mocks/propose_to_import_call_mock.rs b/tee-worker/enclave-runtime/src/test/mocks/propose_to_import_call_mock.rs new file mode 100644 index 0000000000..1621a206eb --- /dev/null +++ b/tee-worker/enclave-runtime/src/test/mocks/propose_to_import_call_mock.rs @@ -0,0 +1,112 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + Copyright (C) 2017-2019 Baidu, Inc. All Rights Reserved. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +use crate::test::mocks::types::TestBlockImporter; +use codec::{Decode, Encode}; +use itp_ocall_api::{EnclaveOnChainOCallApi, EnclaveSidechainOCallApi, Result}; +use itp_types::{ + storage::StorageEntryVerified, BlockHash, Header as ParentchainHeader, ShardIdentifier, + WorkerRequest, WorkerResponse, H256, +}; +use its_primitives::types::block::SignedBlock as SignedSidechainBlockType; +use its_sidechain::consensus_common::BlockImport; +use sgx_types::SgxResult; +use sp_runtime::{traits::Header as ParentchainHeaderTrait, OpaqueExtrinsic}; +use std::{sync::Arc, vec::Vec}; + +/// OCallApi mock that routes the proposed sidechain blocks directly to the importer, +/// short circuiting all the RPC calls. +#[derive(Clone)] +pub struct ProposeToImportOCallApi { + parentchain_header: ParentchainHeader, + block_importer: Arc, +} + +impl ProposeToImportOCallApi { + pub fn new( + parentchain_header: ParentchainHeader, + block_importer: Arc, + ) -> Self { + ProposeToImportOCallApi { parentchain_header, block_importer } + } +} + +impl EnclaveOnChainOCallApi for ProposeToImportOCallApi { + fn send_to_parentchain(&self, _extrinsics: Vec) -> SgxResult<()> { + Ok(()) + } + + fn worker_request( + &self, + _req: Vec, + ) -> SgxResult>> { + todo!() + } + + fn get_storage_verified, V: Decode>( + &self, + _storage_hash: Vec, + _header: &H, + ) -> Result> { + todo!() + } + + fn get_multiple_storages_verified, V: Decode>( + &self, + _storage_hashes: Vec>, + _header: &H, + ) -> Result>> { + todo!() + } +} + +impl EnclaveSidechainOCallApi for ProposeToImportOCallApi { + fn propose_sidechain_blocks( + &self, + signed_blocks: Vec, + ) -> SgxResult<()> { + let decoded_signed_blocks: Vec = signed_blocks + .iter() + .map(|sb| sb.encode()) + .map(|e| SignedSidechainBlockType::decode(&mut e.as_slice()).unwrap()) + .collect(); + + for signed_block in decoded_signed_blocks { + self.block_importer + .import_block(signed_block, &self.parentchain_header) + .unwrap(); + } + Ok(()) + } + + fn store_sidechain_blocks( + &self, + _signed_blocks: Vec, + ) -> SgxResult<()> { + Ok(()) + } + + fn fetch_sidechain_blocks_from_peer( + &self, + _last_imported_block_hash: BlockHash, + _maybe_until_block_hash: Option, + _shard_identifier: ShardIdentifier, + ) -> SgxResult> { + Ok(Vec::new()) + } +} diff --git a/tee-worker/enclave-runtime/src/test/mocks/rpc_responder_mock.rs b/tee-worker/enclave-runtime/src/test/mocks/rpc_responder_mock.rs new file mode 100644 index 0000000000..74e22a29e1 --- /dev/null +++ b/tee-worker/enclave-runtime/src/test/mocks/rpc_responder_mock.rs @@ -0,0 +1,49 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +use itc_direct_rpc_server::{DirectRpcResult, RpcHash, SendRpcResponse}; +use itp_types::TrustedOperationStatus; +use std::{marker::PhantomData, vec::Vec}; + +pub struct RpcResponderMock { + _hash: PhantomData, +} + +impl RpcResponderMock { + pub fn new() -> Self { + RpcResponderMock { _hash: PhantomData } + } +} + +impl SendRpcResponse for RpcResponderMock +where + Hash: RpcHash, +{ + type Hash = Hash; + + fn update_status_event( + &self, + _hash: Self::Hash, + _status_update: TrustedOperationStatus, + ) -> DirectRpcResult<()> { + Ok(()) + } + + fn send_state(&self, _hash: Self::Hash, _state_encoded: Vec) -> DirectRpcResult<()> { + Ok(()) + } +} diff --git a/tee-worker/enclave-runtime/src/test/mocks/types.rs b/tee-worker/enclave-runtime/src/test/mocks/types.rs new file mode 100644 index 0000000000..fb6d479606 --- /dev/null +++ b/tee-worker/enclave-runtime/src/test/mocks/types.rs @@ -0,0 +1,96 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + Copyright (C) 2017-2019 Baidu, Inc. All Rights Reserved. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +//! Type definitions for testing. Includes various mocks. + +use crate::test::mocks::rpc_responder_mock::RpcResponderMock; +use ita_sgx_runtime::Runtime; +use ita_stf::{Getter, Stf, TrustedCallSigned}; +use itc_parentchain::block_import_dispatcher::trigger_parentchain_block_import_mock::TriggerParentchainBlockImportMock; +use itp_node_api::metadata::{metadata_mocks::NodeMetadataMock, provider::NodeMetadataRepository}; +use itp_sgx_crypto::{mocks::KeyRepositoryMock, Aes}; +use itp_sgx_externalities::SgxExternalities; +use itp_stf_executor::executor::StfExecutor; +use itp_test::mock::{ + handle_state_mock::HandleStateMock, metrics_ocall_mock::MetricsOCallMock, + onchain_mock::OnchainMock, +}; +use itp_top_pool::basic_pool::BasicPool; +use itp_top_pool_author::{api::SidechainApi, author::Author, top_filter::AllowAllTopsFilter}; +use itp_types::{Block as ParentchainBlock, SignedBlock as SignedParentchainBlock}; +use its_primitives::types::{Block as SidechainBlock, SignedBlock as SignedSidechainBlock}; +use its_sidechain::{ + aura::block_importer::BlockImporter, block_composer::BlockComposer, state::SidechainDB, +}; +use primitive_types::H256; +use sgx_crypto_helper::rsa3072::Rsa3072KeyPair; +use sp_core::ed25519 as spEd25519; + +pub type TestSigner = spEd25519::Pair; +pub type TestShieldingKey = Rsa3072KeyPair; +pub type TestStateKey = Aes; + +pub type TestGetter = Getter; +pub type TestCall = TrustedCallSigned; +pub type TestStf = Stf; + +pub type TestShieldingKeyRepo = KeyRepositoryMock; + +pub type TestStateKeyRepo = KeyRepositoryMock; + +pub type TestStateHandler = HandleStateMock; + +pub type TestSidechainDb = SidechainDB; + +pub type TestOCallApi = OnchainMock; + +pub type TestParentchainBlockImportTrigger = + TriggerParentchainBlockImportMock; + +pub type TestNodeMetadataRepository = NodeMetadataRepository; + +pub type TestStfExecutor = + StfExecutor; + +pub type TestRpcResponder = RpcResponderMock; + +pub type TestTopPool = + BasicPool, ParentchainBlock, TestRpcResponder>; + +pub type TestTopPoolAuthor = Author< + TestTopPool, + AllowAllTopsFilter, + TestStateHandler, + TestShieldingKeyRepo, + MetricsOCallMock, +>; + +pub type TestBlockComposer = + BlockComposer; + +pub type TestBlockImporter = BlockImporter< + TestSigner, + ParentchainBlock, + SignedSidechainBlock, + TestOCallApi, + TestSidechainDb, + HandleStateMock, + TestStateKeyRepo, + TestTopPoolAuthor, + TestParentchainBlockImportTrigger, +>; diff --git a/tee-worker/enclave-runtime/src/test/mod.rs b/tee-worker/enclave-runtime/src/test/mod.rs new file mode 100644 index 0000000000..a78d648b06 --- /dev/null +++ b/tee-worker/enclave-runtime/src/test/mod.rs @@ -0,0 +1,35 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + Copyright (C) 2017-2019 Baidu, Inc. All Rights Reserved. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +pub mod cert_tests; +pub mod direct_rpc_tests; +pub mod enclave_signer_tests; +#[cfg(feature = "evm")] +pub mod evm_pallet_tests; +pub mod fixtures; +pub mod ipfs_tests; +pub mod mocks; +pub mod on_chain_ocall_tests; +pub mod sidechain_aura_tests; +pub mod sidechain_event_tests; +mod state_getter_tests; +pub mod tests_main; +pub mod top_pool_tests; + +#[cfg(feature = "teeracle")] +pub mod teeracle_tests; diff --git a/tee-worker/enclave-runtime/src/test/on_chain_ocall_tests.rs b/tee-worker/enclave-runtime/src/test/on_chain_ocall_tests.rs new file mode 100644 index 0000000000..403b9734a8 --- /dev/null +++ b/tee-worker/enclave-runtime/src/test/on_chain_ocall_tests.rs @@ -0,0 +1,46 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + Copyright (C) 2017-2019 Baidu, Inc. All Rights Reserved. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +use crate::ocall::OcallApi; +use itp_ocall_api::EnclaveOnChainOCallApi; +use itp_types::{WorkerRequest, WorkerResponse}; +use log::*; +use std::vec::Vec; +use substrate_api_client::utils::storage_key; + +#[allow(unused)] +fn test_ocall_worker_request() { + info!("testing ocall_worker_request. Hopefully integritee-node is running..."); + let requests = + vec![WorkerRequest::ChainStorage(storage_key("Balances", "TotalIssuance").0, None)]; + + let mut resp: Vec>> = match OcallApi.worker_request(requests) { + Ok(response) => response, + Err(e) => panic!("Worker response decode failed. Error: {:?}", e), + }; + + let first = resp.pop().unwrap(); + info!("Worker response: {:?}", first); + + let (total_issuance, proof) = match first { + WorkerResponse::ChainStorage(_storage_key, value, proof) => (value, proof), + }; + + info!("Total Issuance is: {:?}", total_issuance); + info!("Proof: {:?}", proof) +} diff --git a/tee-worker/enclave-runtime/src/test/sidechain_aura_tests.rs b/tee-worker/enclave-runtime/src/test/sidechain_aura_tests.rs new file mode 100644 index 0000000000..e1b271bc88 --- /dev/null +++ b/tee-worker/enclave-runtime/src/test/sidechain_aura_tests.rs @@ -0,0 +1,259 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + Copyright (C) 2017-2019 Baidu, Inc. All Rights Reserved. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +use crate::{ + test::{ + fixtures::{ + components::{ + create_ocall_api, create_top_pool, encrypt_trusted_operation, sign_trusted_call, + }, + initialize_test_state::init_state, + test_setup::{enclave_call_signer, TestStf}, + }, + mocks::{propose_to_import_call_mock::ProposeToImportOCallApi, types::*}, + }, + top_pool_execution::{exec_aura_on_slot, send_blocks_and_extrinsics}, +}; +use codec::Decode; +use ita_stf::{ + test_genesis::{endowed_account, second_endowed_account, unendowed_account}, + Balance, StatePayload, TrustedCall, TrustedOperation, +}; +use itc_parentchain::light_client::mocks::validator_access_mock::ValidatorAccessMock; +use itc_parentchain_test::parentchain_header_builder::ParentchainHeaderBuilder; +use itp_extrinsics_factory::mock::ExtrinsicsFactoryMock; +use itp_node_api::metadata::{metadata_mocks::NodeMetadataMock, provider::NodeMetadataRepository}; +use itp_ocall_api::EnclaveAttestationOCallApi; +use itp_settings::{ + sidechain::SLOT_DURATION, + worker_mode::{ProvideWorkerMode, WorkerMode, WorkerModeProvider}, +}; +use itp_sgx_crypto::{Aes, ShieldingCryptoEncrypt, StateCrypto}; +use itp_sgx_externalities::SgxExternalitiesDiffType; +use itp_stf_interface::system_pallet::{SystemPalletAccountInterface, SystemPalletEventInterface}; +use itp_stf_state_handler::handle_state::HandleState; +use itp_test::mock::{handle_state_mock::HandleStateMock, metrics_ocall_mock::MetricsOCallMock}; +use itp_time_utils::duration_now; +use itp_top_pool_author::{top_filter::AllowAllTopsFilter, traits::AuthorApi}; +use itp_types::{AccountId, Block as ParentchainBlock, ShardIdentifier}; +use its_block_verification::slot::slot_from_timestamp_and_duration; +use its_primitives::{traits::Block, types::SignedBlock as SignedSidechainBlock}; +use its_sidechain::{ + aura::proposer_factory::ProposerFactory, slots::SlotInfo, state::SidechainState, +}; +use jsonrpc_core::futures::executor; +use log::*; +use primitive_types::H256; +use sgx_crypto_helper::RsaKeyPair; +use sp_core::{ed25519, Pair}; +use std::{sync::Arc, vec, vec::Vec}; + +/// Integration test for sidechain block production and block import. +/// (requires Sidechain mode) +/// +/// - Create trusted calls and add them to the TOP pool. +/// - Run AURA on a valid and claimed slot, which executes the trusted operations and produces a new block. +/// - Import the new sidechain block, which updates the state. +pub fn produce_sidechain_block_and_import_it() { + // Test can only be run in Sidechain mode + if WorkerModeProvider::worker_mode() != WorkerMode::Sidechain { + info!("Ignoring sidechain block production test: Not in sidechain mode"); + return + } + + let _ = env_logger::builder().is_test(true).try_init(); + info!("Setting up test."); + + let signer = TestSigner::from_seed(b"42315678901234567890123456789012"); + let shielding_key = TestShieldingKey::new().unwrap(); + let state_key = TestStateKey::new([3u8; 16], [1u8; 16]); + let shielding_key_repo = Arc::new(TestShieldingKeyRepo::new(shielding_key)); + let state_key_repo = Arc::new(TestStateKeyRepo::new(state_key)); + let parentchain_header = ParentchainHeaderBuilder::default().build(); + + let ocall_api = create_ocall_api(&parentchain_header, &signer); + + info!("Initializing state and shard.."); + let state_handler = Arc::new(TestStateHandler::default()); + let enclave_call_signer = enclave_call_signer(&shielding_key); + let (_, shard_id) = init_state(state_handler.as_ref(), enclave_call_signer.public().into()); + let shards = vec![shard_id]; + + let node_metadata_repo = Arc::new(NodeMetadataRepository::new(NodeMetadataMock::new())); + let stf_executor = Arc::new(TestStfExecutor::new( + ocall_api.clone(), + state_handler.clone(), + node_metadata_repo.clone(), + )); + let top_pool = create_top_pool(); + + let top_pool_author = Arc::new(TestTopPoolAuthor::new( + top_pool, + AllowAllTopsFilter {}, + state_handler.clone(), + shielding_key_repo, + Arc::new(MetricsOCallMock::default()), + )); + let parentchain_block_import_trigger = Arc::new(TestParentchainBlockImportTrigger::default()); + let block_importer = Arc::new(TestBlockImporter::new( + state_handler.clone(), + state_key_repo.clone(), + top_pool_author.clone(), + parentchain_block_import_trigger.clone(), + ocall_api.clone(), + )); + let block_composer = Arc::new(TestBlockComposer::new(signer.clone(), state_key_repo.clone())); + let proposer_environment = + ProposerFactory::new(top_pool_author.clone(), stf_executor.clone(), block_composer); + let extrinsics_factory = ExtrinsicsFactoryMock::default(); + let validator_access = ValidatorAccessMock::default(); + + info!("Create trusted operations.."); + let sender = endowed_account(); + let sender_with_low_balance = second_endowed_account(); + let receiver = unendowed_account(); + let transfered_amount: Balance = 1000; + let trusted_operation = encrypted_trusted_operation_transfer_balance( + ocall_api.as_ref(), + &shard_id, + &shielding_key, + sender, + receiver.public().into(), + transfered_amount, + ); + let invalid_trusted_operation = encrypted_trusted_operation_transfer_balance( + ocall_api.as_ref(), + &shard_id, + &shielding_key, + sender_with_low_balance, + receiver.public().into(), + 200000, + ); + info!("Add trusted operations to TOP pool.."); + executor::block_on(top_pool_author.submit_top(trusted_operation, shard_id)).unwrap(); + executor::block_on(top_pool_author.submit_top(invalid_trusted_operation, shard_id)).unwrap(); + + // Ensure we have exactly two trusted calls in our TOP pool, and no getters. + assert_eq!(2, top_pool_author.get_pending_trusted_calls(shard_id).len()); + assert!(top_pool_author.get_pending_trusted_getters(shard_id).is_empty()); + + info!("Setup AURA SlotInfo"); + let timestamp = duration_now(); + let slot = slot_from_timestamp_and_duration(duration_now(), SLOT_DURATION); + let ends_at = timestamp + SLOT_DURATION; + let slot_info = + SlotInfo::new(slot, timestamp, SLOT_DURATION, ends_at, parentchain_header.clone()); + + info!("Test setup is done."); + + let state_hash_before_block_production = get_state_hash(state_handler.as_ref(), &shard_id); + + info!("Executing AURA on slot.."); + let (blocks, opaque_calls) = + exec_aura_on_slot::<_, ParentchainBlock, SignedSidechainBlock, _, _, _>( + slot_info, + signer, + ocall_api.clone(), + parentchain_block_import_trigger.clone(), + proposer_environment, + shards, + ) + .unwrap(); + + assert_eq!(1, blocks.len()); + assert_eq!( + state_hash_before_block_production, + get_state_hash(state_handler.as_ref(), &shard_id) + ); + + let (apriori_state_hash_in_block, aposteriori_state_hash_in_block) = + get_state_hashes_from_block(blocks.first().unwrap(), &state_key); + assert_ne!(state_hash_before_block_production, aposteriori_state_hash_in_block); + assert_eq!(state_hash_before_block_production, apriori_state_hash_in_block); + + // Ensure we have triggered the parentchain block import, because we claimed the slot. + assert!(parentchain_block_import_trigger.has_import_been_called()); + + // Ensure that invalid calls are removed from pool. Valid calls should only be removed upon block import. + assert_eq!(1, top_pool_author.get_pending_trusted_calls(shard_id).len()); + + info!("Executed AURA successfully. Sending blocks and extrinsics.."); + let propose_to_block_import_ocall_api = + Arc::new(ProposeToImportOCallApi::new(parentchain_header, block_importer)); + + send_blocks_and_extrinsics::( + blocks, + opaque_calls, + propose_to_block_import_ocall_api, + &validator_access, + &extrinsics_factory, + ) + .unwrap(); + + // After importing the sidechain block, the trusted operation should be removed. + assert!(top_pool_author.get_pending_trusted_calls(shard_id).is_empty()); + + // After importing the block, the state hash must be changed. + // We don't have a way to directly compare state hashes, because calculating the state hash + // would also involve applying set_last_block action, which updates the state upon import. + assert_ne!( + state_hash_before_block_production, + get_state_hash(state_handler.as_ref(), &shard_id) + ); + + let mut state = state_handler.load(&shard_id).unwrap(); + let free_balance = TestStf::get_account_data(&mut state, &receiver.public().into()).free; + assert_eq!(free_balance, transfered_amount); + assert!(TestStf::get_event_count(&mut state) > 0); + assert!(TestStf::get_events(&mut state).len() > 0); +} + +fn encrypted_trusted_operation_transfer_balance< + AttestationApi: EnclaveAttestationOCallApi, + ShieldingKey: ShieldingCryptoEncrypt, +>( + attestation_api: &AttestationApi, + shard_id: &ShardIdentifier, + shielding_key: &ShieldingKey, + from: ed25519::Pair, + to: AccountId, + amount: Balance, +) -> Vec { + let call = TrustedCall::balance_transfer(from.public().into(), to, amount); + let call_signed = sign_trusted_call(&call, attestation_api, shard_id, from); + let trusted_operation = TrustedOperation::direct_call(call_signed); + encrypt_trusted_operation(shielding_key, &trusted_operation) +} + +fn get_state_hashes_from_block( + signed_block: &SignedSidechainBlock, + state_key: &Aes, +) -> (H256, H256) { + let mut encrypted_state_diff = signed_block.block.block_data().encrypted_state_diff.clone(); + state_key.decrypt(&mut encrypted_state_diff).unwrap(); + let decoded_state = + StatePayload::::decode(&mut encrypted_state_diff.as_slice()) + .unwrap(); + (decoded_state.state_hash_apriori(), decoded_state.state_hash_aposteriori()) +} + +fn get_state_hash(state_handler: &HandleStateMock, shard_id: &ShardIdentifier) -> H256 { + let state = state_handler.load(shard_id).unwrap(); + let sidechain_state = TestSidechainDb::new(state); + sidechain_state.state_hash() +} diff --git a/tee-worker/enclave-runtime/src/test/sidechain_event_tests.rs b/tee-worker/enclave-runtime/src/test/sidechain_event_tests.rs new file mode 100644 index 0000000000..ff9407ffc9 --- /dev/null +++ b/tee-worker/enclave-runtime/src/test/sidechain_event_tests.rs @@ -0,0 +1,169 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + Copyright (C) 2017-2019 Baidu, Inc. All Rights Reserved. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +use crate::{ + test::{ + fixtures::{ + components::{create_ocall_api, create_top_pool}, + initialize_test_state::init_state, + test_setup::{enclave_call_signer, TestStf}, + }, + mocks::{propose_to_import_call_mock::ProposeToImportOCallApi, types::*}, + }, + top_pool_execution::{exec_aura_on_slot, send_blocks_and_extrinsics}, +}; +use ita_sgx_runtime::Runtime; +use ita_stf::helpers::set_block_number; +use itc_parentchain::light_client::mocks::validator_access_mock::ValidatorAccessMock; +use itc_parentchain_test::parentchain_header_builder::ParentchainHeaderBuilder; +use itp_extrinsics_factory::mock::ExtrinsicsFactoryMock; +use itp_node_api::metadata::{metadata_mocks::NodeMetadataMock, provider::NodeMetadataRepository}; +use itp_settings::{ + sidechain::SLOT_DURATION, + worker_mode::{ProvideWorkerMode, WorkerMode, WorkerModeProvider}, +}; +use itp_sgx_externalities::SgxExternalitiesTrait; +use itp_stf_interface::system_pallet::SystemPalletEventInterface; +use itp_stf_state_handler::handle_state::HandleState; +use itp_test::mock::metrics_ocall_mock::MetricsOCallMock; +use itp_time_utils::duration_now; +use itp_top_pool_author::top_filter::AllowAllTopsFilter; +use itp_types::Block as ParentchainBlock; +use its_block_verification::slot::slot_from_timestamp_and_duration; +use its_primitives::types::SignedBlock as SignedSidechainBlock; +use its_sidechain::{aura::proposer_factory::ProposerFactory, slots::SlotInfo}; +use log::*; +use primitive_types::H256; +use sgx_crypto_helper::RsaKeyPair; +use sp_core::Pair; +use std::{sync::Arc, vec}; + +/// Integration test to ensure the events are reset upon block import. +/// Otherwise we will have an ever growing state. +/// (requires Sidechain mode) +pub fn ensure_events_get_reset_upon_block_proposal() { + // Test can only be run in Sidechain mode + if WorkerModeProvider::worker_mode() != WorkerMode::Sidechain { + info!("Ignoring sidechain block production test: Not in sidechain mode"); + return + } + + let _ = env_logger::builder().is_test(true).try_init(); + info!("Setting up test."); + + let signer = TestSigner::from_seed(b"42315678901234567890123456789012"); + let shielding_key = TestShieldingKey::new().unwrap(); + let state_key = TestStateKey::new([3u8; 16], [1u8; 16]); + let shielding_key_repo = Arc::new(TestShieldingKeyRepo::new(shielding_key)); + let state_key_repo = Arc::new(TestStateKeyRepo::new(state_key)); + let parentchain_header = ParentchainHeaderBuilder::default().build(); + + let ocall_api = create_ocall_api(&parentchain_header, &signer); + + info!("Initializing state and shard.."); + let state_handler = Arc::new(TestStateHandler::default()); + let enclave_call_signer = enclave_call_signer(&shielding_key); + let (_, shard_id) = init_state(state_handler.as_ref(), enclave_call_signer.public().into()); + let shards = vec![shard_id]; + + let node_metadata_repo = Arc::new(NodeMetadataRepository::new(NodeMetadataMock::new())); + let stf_executor = Arc::new(TestStfExecutor::new( + ocall_api.clone(), + state_handler.clone(), + node_metadata_repo.clone(), + )); + let top_pool = create_top_pool(); + + let top_pool_author = Arc::new(TestTopPoolAuthor::new( + top_pool, + AllowAllTopsFilter {}, + state_handler.clone(), + shielding_key_repo, + Arc::new(MetricsOCallMock::default()), + )); + let parentchain_block_import_trigger = Arc::new(TestParentchainBlockImportTrigger::default()); + let block_importer = Arc::new(TestBlockImporter::new( + state_handler.clone(), + state_key_repo.clone(), + top_pool_author.clone(), + parentchain_block_import_trigger.clone(), + ocall_api.clone(), + )); + let block_composer = Arc::new(TestBlockComposer::new(signer.clone(), state_key_repo.clone())); + let proposer_environment = + ProposerFactory::new(top_pool_author.clone(), stf_executor.clone(), block_composer); + let extrinsics_factory = ExtrinsicsFactoryMock::default(); + let validator_access = ValidatorAccessMock::default(); + + // Add some events to the state. + let topic_hash = H256::from([7; 32]); + let event = frame_system::Event::::CodeUpdated; + let (lock, mut state) = state_handler.load_for_mutation(&shard_id).unwrap(); + state.execute_with(|| { + set_block_number(10); + frame_system::Pallet::::deposit_event_indexed( + &[topic_hash], + ita_sgx_runtime::Event::System(event), + ) + }); + state_handler.write_after_mutation(state.clone(), lock, &shard_id).unwrap(); + + // Check if state now really contains events and topics. + let mut state = state_handler.load(&shard_id).unwrap(); + assert_eq!(TestStf::get_event_count(&mut state), 1); + assert_eq!(TestStf::get_events(&mut state).len(), 1); + assert_eq!(TestStf::get_event_topics(&mut state, &topic_hash).len(), 1); + + info!("Setup AURA SlotInfo"); + let timestamp = duration_now(); + let slot = slot_from_timestamp_and_duration(duration_now(), SLOT_DURATION); + let ends_at = timestamp + SLOT_DURATION; + let slot_info = + SlotInfo::new(slot, timestamp, SLOT_DURATION, ends_at, parentchain_header.clone()); + + info!("Executing AURA on slot.."); + let (blocks, opaque_calls) = + exec_aura_on_slot::<_, ParentchainBlock, SignedSidechainBlock, _, _, _>( + slot_info, + signer, + ocall_api.clone(), + parentchain_block_import_trigger.clone(), + proposer_environment, + shards, + ) + .unwrap(); + + info!("Executed AURA successfully. Sending blocks and extrinsics.."); + let propose_to_block_import_ocall_api = + Arc::new(ProposeToImportOCallApi::new(parentchain_header, block_importer)); + + send_blocks_and_extrinsics::( + blocks, + opaque_calls, + propose_to_block_import_ocall_api, + &validator_access, + &extrinsics_factory, + ) + .unwrap(); + + // Ensure events have been reset. + let mut state = state_handler.load(&shard_id).unwrap(); + assert_eq!(TestStf::get_event_count(&mut state), 0); + assert_eq!(TestStf::get_event_topics(&mut state, &topic_hash).len(), 0); + assert_eq!(TestStf::get_events(&mut state).len(), 0); +} diff --git a/tee-worker/enclave-runtime/src/test/state_getter_tests.rs b/tee-worker/enclave-runtime/src/test/state_getter_tests.rs new file mode 100644 index 0000000000..172468170c --- /dev/null +++ b/tee-worker/enclave-runtime/src/test/state_getter_tests.rs @@ -0,0 +1,50 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + Copyright (C) 2017-2019 Baidu, Inc. All Rights Reserved. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +use codec::Decode; +use ita_sgx_runtime::Runtime; +use ita_stf::{ + test_genesis::{endowed_account, test_genesis_setup, ENDOWED_ACC_FUNDS}, + Balance, Getter, Stf, TrustedCallSigned, TrustedGetter, +}; +use itp_sgx_externalities::SgxExternalities; +use itp_stf_executor::state_getter::{GetState, StfStateGetter}; +use sp_core::Pair; + +type TestState = SgxExternalities; +type TestStf = Stf; +type TestStfStateGetter = StfStateGetter; + +pub fn state_getter_works() { + let sender = endowed_account(); + let signed_getter = TrustedGetter::free_balance(sender.public().into()).sign(&sender.into()); + let mut state = test_state(); + + let encoded_balance = + TestStfStateGetter::get_state(&signed_getter, &mut state).unwrap().unwrap(); + + let balance = Balance::decode(&mut encoded_balance.as_slice()).unwrap(); + + assert_eq!(balance, ENDOWED_ACC_FUNDS); +} + +fn test_state() -> TestState { + let mut state = TestState::default(); + test_genesis_setup(&mut state); + state +} diff --git a/tee-worker/enclave-runtime/src/test/teeracle_tests.rs b/tee-worker/enclave-runtime/src/test/teeracle_tests.rs new file mode 100644 index 0000000000..3d86e04f2b --- /dev/null +++ b/tee-worker/enclave-runtime/src/test/teeracle_tests.rs @@ -0,0 +1,49 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + Copyright (C) 2017-2019 Baidu, Inc. All Rights Reserved. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +use codec::alloc::string::ToString; +use ita_exchange_oracle::{ + create_coin_gecko_oracle, create_coin_market_cap_oracle, types::TradingPair, GetExchangeRate, +}; +use itp_test::mock::metrics_ocall_mock::MetricsOCallMock; +use std::sync::Arc; + +pub(super) fn test_verify_get_exchange_rate_from_coin_gecko_works() { + // Get the exchange rate + let trading_pair = + TradingPair { crypto_currency: "DOT".to_string(), fiat_currency: "USD".to_string() }; + + let coin_gecko_oracle = create_coin_gecko_oracle(Arc::new(MetricsOCallMock::default())); + + let result = coin_gecko_oracle.get_exchange_rate(trading_pair.clone()); + assert!(result.is_ok()); +} + +/// Get exchange rate from coin market cap. Requires API key (therefore not suited for unit testing). +#[allow(unused)] +pub(super) fn test_verify_get_exchange_rate_from_coin_market_cap_works() { + // Get the exchange rate + let trading_pair = + TradingPair { crypto_currency: "DOT".to_string(), fiat_currency: "USD".to_string() }; + + let coin_market_cap_oracle = + create_coin_market_cap_oracle(Arc::new(MetricsOCallMock::default())); + + let result = coin_market_cap_oracle.get_exchange_rate(trading_pair.clone()); + assert!(result.is_ok()); +} diff --git a/tee-worker/enclave-runtime/src/test/tests_main.rs b/tee-worker/enclave-runtime/src/test/tests_main.rs new file mode 100644 index 0000000000..d18b72e422 --- /dev/null +++ b/tee-worker/enclave-runtime/src/test/tests_main.rs @@ -0,0 +1,732 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +#[cfg(feature = "evm")] +use crate::test::evm_pallet_tests; + +use crate::{ + rpc, + sync::tests::{enclave_rw_lock_works, sidechain_rw_lock_works}, + test::{ + cert_tests::*, + direct_rpc_tests, enclave_signer_tests, + fixtures::test_setup::{ + enclave_call_signer, test_setup, TestStf, TestStfExecutor, TestTopPoolAuthor, + }, + mocks::types::TestStateKeyRepo, + sidechain_aura_tests, sidechain_event_tests, state_getter_tests, top_pool_tests, + }, + tls_ra, +}; +use codec::Decode; +use ita_sgx_runtime::Parentchain; +use ita_stf::{ + helpers::{account_key_hash, set_block_number}, + stf_sgx_tests, + test_genesis::{endowed_account as funded_pair, unendowed_account}, + AccountInfo, Getter, ShardIdentifier, State, StatePayload, TrustedCall, TrustedCallSigned, + TrustedGetter, TrustedOperation, +}; +use itp_node_api::metadata::{metadata_mocks::NodeMetadataMock, provider::NodeMetadataRepository}; +use itp_sgx_crypto::{Aes, StateCrypto}; +use itp_sgx_externalities::{SgxExternalities, SgxExternalitiesDiffType, SgxExternalitiesTrait}; +use itp_stf_executor::{ + executor_tests as stf_executor_tests, traits::StateUpdateProposer, BatchExecutionResult, +}; +use itp_stf_interface::{ + parentchain_pallet::ParentchainPalletInterface, + system_pallet::{SystemPalletAccountInterface, SystemPalletEventInterface}, + StateCallInterface, +}; +use itp_stf_state_handler::handle_state::HandleState; +use itp_test::mock::handle_state_mock; +use itp_top_pool_author::{test_utils::submit_operation_to_top_pool, traits::AuthorApi}; +use itp_types::{AccountId, Block, Header}; +use its_primitives::{ + traits::{ + Block as BlockTrait, BlockData, Header as SidechainHeaderTrait, + SignedBlock as SignedBlockTrait, + }, + types::block::SignedBlock, +}; +use its_sidechain::{ + block_composer::{BlockComposer, ComposeBlock}, + state::{SidechainDB, SidechainState, SidechainSystemExt}, +}; +use sgx_tunittest::*; +use sgx_types::size_t; +use sp_core::{crypto::Pair, ed25519 as spEd25519, H256}; +use sp_runtime::traits::Header as HeaderT; +use std::{string::String, sync::Arc, time::Duration, vec::Vec}; + +#[no_mangle] +pub extern "C" fn test_main_entrance() -> size_t { + rsgx_unit_tests!( + itp_attestation_handler::attestation_handler::tests::decode_spid_works, + stf_sgx_tests::enclave_account_initialization_works, + stf_sgx_tests::shield_funds_increments_signer_account_nonce, + stf_sgx_tests::test_root_account_exists_after_initialization, + itp_stf_state_handler::test::sgx_tests::test_write_and_load_state_works, + itp_stf_state_handler::test::sgx_tests::test_sgx_state_decode_encode_works, + itp_stf_state_handler::test::sgx_tests::test_encrypt_decrypt_state_type_works, + itp_stf_state_handler::test::sgx_tests::test_write_access_locks_read_until_finished, + itp_stf_state_handler::test::sgx_tests::test_ensure_subsequent_state_loads_have_same_hash, + itp_stf_state_handler::test::sgx_tests::test_state_handler_file_backend_is_initialized, + itp_stf_state_handler::test::sgx_tests::test_multiple_state_updates_create_snapshots_up_to_cache_size, + itp_stf_state_handler::test::sgx_tests::test_state_files_from_handler_can_be_loaded_again, + itp_stf_state_handler::test::sgx_tests::test_file_io_get_state_hash_works, + itp_stf_state_handler::test::sgx_tests::test_list_state_ids_ignores_files_not_matching_the_pattern, + itp_stf_state_handler::test::sgx_tests::test_in_memory_state_initializes_from_shard_directory, + test_compose_block, + test_submit_trusted_call_to_top_pool, + test_submit_trusted_getter_to_top_pool, + test_differentiate_getter_and_call_works, + test_create_block_and_confirmation_works, + // needs node to be running.. unit tests? + // test_ocall_worker_request, + test_create_state_diff, + test_executing_call_updates_account_nonce, + test_call_set_update_parentchain_block, + test_invalid_nonce_call_is_not_executed, + test_signature_must_match_public_sender_in_call, + test_non_root_shielding_call_is_not_executed, + test_shielding_call_with_enclave_self_is_executed, + test_retrieve_events, + test_retrieve_event_count, + test_reset_events, + rpc::worker_api_direct::tests::test_given_io_handler_methods_then_retrieve_all_names_as_string, + handle_state_mock::tests::initialized_shards_list_is_empty, + handle_state_mock::tests::shard_exists_after_inserting, + handle_state_mock::tests::from_shard_works, + handle_state_mock::tests::initialize_creates_default_state, + handle_state_mock::tests::load_mutate_and_write_works, + handle_state_mock::tests::ensure_subsequent_state_loads_have_same_hash, + handle_state_mock::tests::ensure_encode_and_encrypt_does_not_affect_state_hash, + // mra cert tests + test_verify_mra_cert_should_work, + test_verify_wrong_cert_is_err, + test_given_wrong_platform_info_when_verifying_attestation_report_then_return_error, + // sync tests + sidechain_rw_lock_works, + enclave_rw_lock_works, + // unit tests of stf_executor + stf_executor_tests::execute_update_works, + stf_executor_tests::propose_state_update_always_executes_preprocessing_step, + stf_executor_tests::propose_state_update_executes_no_trusted_calls_given_no_time, + stf_executor_tests::propose_state_update_executes_only_one_trusted_call_given_not_enough_time, + stf_executor_tests::propose_state_update_executes_all_calls_given_enough_time, + enclave_signer_tests::enclave_signer_signatures_are_valid, + enclave_signer_tests::derive_key_is_deterministic, + state_getter_tests::state_getter_works, + // sidechain integration tests + sidechain_aura_tests::produce_sidechain_block_and_import_it, + sidechain_event_tests::ensure_events_get_reset_upon_block_proposal, + top_pool_tests::process_indirect_call_in_top_pool, + top_pool_tests::submit_shielding_call_to_top_pool, + // tls_ra unit tests + tls_ra::seal_handler::test::seal_shielding_key_works, + tls_ra::seal_handler::test::seal_shielding_key_fails_for_invalid_key, + tls_ra::seal_handler::test::unseal_seal_shielding_key_works, + tls_ra::seal_handler::test::seal_state_key_works, + tls_ra::seal_handler::test::seal_state_key_fails_for_invalid_key, + tls_ra::seal_handler::test::unseal_seal_state_key_works, + tls_ra::seal_handler::test::seal_state_works, + tls_ra::seal_handler::test::seal_state_fails_for_invalid_state, + tls_ra::seal_handler::test::unseal_seal_state_works, + tls_ra::tests::test_tls_ra_server_client_networking, + tls_ra::tests::test_state_and_key_provisioning, + // RPC tests + direct_rpc_tests::get_state_request_works, + + // EVM tests + run_evm_tests, + + // these unit test (?) need an ipfs node running.. + // ipfs::test_creates_ipfs_content_struct_works, + // ipfs::test_verification_ok_for_correct_content, + // ipfs::test_verification_fails_for_incorrect_content, + // test_ocall_read_write_ipfs, + + // Teeracle tests + run_teeracle_tests, + ) +} + +#[cfg(feature = "teeracle")] +fn run_teeracle_tests() { + use super::teeracle_tests::*; + test_verify_get_exchange_rate_from_coin_gecko_works(); + // Disabled - requires API key, cannot run locally + //test_verify_get_exchange_rate_from_coin_market_cap_works(); +} + +#[cfg(not(feature = "teeracle"))] +fn run_teeracle_tests() {} + +#[cfg(feature = "evm")] +fn run_evm_tests() { + evm_pallet_tests::test_evm_call(); + evm_pallet_tests::test_evm_counter(); + evm_pallet_tests::test_evm_create(); + evm_pallet_tests::test_evm_create2(); +} +#[cfg(not(feature = "evm"))] +fn run_evm_tests() {} + +fn test_compose_block() { + // given + let (_, _, shard, _, _, state_handler, _) = test_setup(); + let block_composer = BlockComposer::::new( + test_account(), + Arc::new(TestStateKeyRepo::new(state_key())), + ); + + let signed_top_hashes: Vec = vec![[94; 32].into(), [1; 32].into()].to_vec(); + + let (lock, state) = state_handler.load_for_mutation(&shard).unwrap(); + let mut db = SidechainDB::::new(state.clone()); + db.set_block_number(&1); + let state_hash_before_execution = db.state_hash(); + state_handler.write_after_mutation(db.ext.clone(), lock, &shard).unwrap(); + + // when + let signed_block = block_composer + .compose_block( + &latest_parentchain_header(), + signed_top_hashes, + shard, + state_hash_before_execution, + db.ext, + ) + .unwrap(); + + // then + assert!(signed_block.verify_signature()); + assert_eq!(signed_block.block().header().block_number(), 1); +} + +fn test_submit_trusted_call_to_top_pool() { + // given + let (top_pool_author, _, shard, mrenclave, shielding_key, ..) = test_setup(); + + let sender = funded_pair(); + + let signed_call = + TrustedCall::balance_set_balance(sender.public().into(), sender.public().into(), 42, 42) + .sign(&sender.into(), 0, &mrenclave, &shard); + let trusted_operation = direct_top(signed_call); + + // when + submit_operation_to_top_pool( + top_pool_author.as_ref(), + &trusted_operation, + &shielding_key, + shard, + ) + .unwrap(); + + let calls = top_pool_author.get_pending_trusted_calls(shard); + + // then + assert_eq!(calls[0], trusted_operation); +} + +// The TOP pool can hold any TrustedOperation, which at the moment also includes Getters. +// However, in reality we don't submit getters to the TOP pool anymore, they are executed immediately. +// The filter set in the TOP pool author prevents getters from being submitted. +// In this test however, we set the filter to `AllowAllTops`, so getters can be submitted. +// We want to keep this back door open, in case we would want to submit getter into the TOP pool again in the future. +fn test_submit_trusted_getter_to_top_pool() { + // given + let (top_pool_author, _, shard, _, shielding_key, ..) = test_setup(); + + let sender = funded_pair(); + + let signed_getter = TrustedGetter::free_balance(sender.public().into()).sign(&sender.into()); + + // when + submit_operation_to_top_pool( + top_pool_author.as_ref(), + &signed_getter.clone().into(), + &shielding_key, + shard, + ) + .unwrap(); + + let getters = top_pool_author.get_pending_trusted_getters(shard); + + // then + assert_eq!(getters[0], TrustedOperation::get(Getter::trusted(signed_getter))); +} + +fn test_differentiate_getter_and_call_works() { + // given + let (top_pool_author, _, shard, mrenclave, shielding_key, ..) = test_setup(); + + // create accounts + let sender = funded_pair(); + + let signed_getter = + TrustedGetter::free_balance(sender.public().into()).sign(&sender.clone().into()); + + let signed_call = + TrustedCall::balance_set_balance(sender.public().into(), sender.public().into(), 42, 42) + .sign(&sender.clone().into(), 0, &mrenclave, &shard); + let trusted_operation = direct_top(signed_call); + + // when + submit_operation_to_top_pool( + top_pool_author.as_ref(), + &signed_getter.clone().into(), + &shielding_key, + shard, + ) + .unwrap(); + submit_operation_to_top_pool( + top_pool_author.as_ref(), + &trusted_operation, + &shielding_key, + shard, + ) + .unwrap(); + + let calls = top_pool_author.get_pending_trusted_calls(shard); + let getters = top_pool_author.get_pending_trusted_getters(shard); + + // then + assert_eq!(calls[0], trusted_operation); + assert_eq!(getters[0], TrustedOperation::get(Getter::trusted(signed_getter))); +} + +fn test_create_block_and_confirmation_works() { + // given + let (top_pool_author, _, shard, mrenclave, shielding_key, _, stf_executor) = test_setup(); + + let block_composer = BlockComposer::::new( + test_account(), + Arc::new(TestStateKeyRepo::new(state_key())), + ); + + let sender = funded_pair(); + let receiver = unfunded_public(); + + let signed_call = TrustedCall::balance_transfer(sender.public().into(), receiver.into(), 1000) + .sign(&sender.into(), 0, &mrenclave, &shard); + let trusted_operation = direct_top(signed_call); + + let top_hash = submit_operation_to_top_pool( + top_pool_author.as_ref(), + &trusted_operation, + &shielding_key, + shard, + ) + .unwrap(); + + // when + let execution_result = execute_trusted_calls(&shard, stf_executor.as_ref(), &top_pool_author); + + let executed_operation_hashes = + execution_result.get_executed_operation_hashes().iter().copied().collect(); + + let signed_block = block_composer + .compose_block( + &latest_parentchain_header(), + executed_operation_hashes, + shard, + execution_result.state_hash_before_execution, + execution_result.state_after_execution, + ) + .unwrap(); + + // then + assert!(signed_block.verify_signature()); + assert_eq!(signed_block.block().header().block_number(), 1); + assert_eq!(signed_block.block().block_data().signed_top_hashes()[0], top_hash); +} + +fn test_create_state_diff() { + // given + let (top_pool_author, _, shard, mrenclave, shielding_key, _, stf_executor) = test_setup(); + + let block_composer = BlockComposer::::new( + test_account(), + Arc::new(TestStateKeyRepo::new(state_key())), + ); + + let sender = funded_pair(); + let receiver = unfunded_public(); + + let signed_call = TrustedCall::balance_transfer(sender.public().into(), receiver.into(), 1000) + .sign(&sender.clone().into(), 0, &mrenclave, &shard); + let trusted_operation = direct_top(signed_call); + + submit_operation_to_top_pool( + top_pool_author.as_ref(), + &trusted_operation, + &shielding_key, + shard, + ) + .unwrap(); + + // when + let execution_result = execute_trusted_calls(&shard, stf_executor.as_ref(), &top_pool_author); + + let executed_operation_hashes = + execution_result.get_executed_operation_hashes().iter().copied().collect(); + + let signed_block = block_composer + .compose_block( + &latest_parentchain_header(), + executed_operation_hashes, + shard, + execution_result.state_hash_before_execution, + execution_result.state_after_execution, + ) + .unwrap(); + + let encrypted_state_diff = encrypted_state_diff_from_encrypted( + signed_block.block().block_data().encrypted_state_diff(), + ); + let state_diff = encrypted_state_diff.state_update(); + + // then + let sender_acc_info: AccountInfo = + get_from_state_diff(&state_diff, &account_key_hash::(&sender.public().into())); + + let receiver_acc_info: AccountInfo = + get_from_state_diff(&state_diff, &account_key_hash::(&receiver.into())); + + // state diff should consist of the following updates: + // (last_hash, sidechain block_number, sender_funds, receiver_funds, [no clear, after polkadot_v0.9.26 update], events) + assert_eq!(state_diff.len(), 6); + assert_eq!(receiver_acc_info.data.free, 1000); + assert_eq!(sender_acc_info.data.free, 1000); +} + +fn test_executing_call_updates_account_nonce() { + // given + let (top_pool_author, _, shard, mrenclave, shielding_key, _, stf_executor) = test_setup(); + + let sender = funded_pair(); + let receiver = unfunded_public(); + + let trusted_operation = + TrustedCall::balance_transfer(sender.public().into(), receiver.into(), 1000) + .sign(&sender.clone().into(), 0, &mrenclave, &shard) + .into_trusted_operation(false); + + submit_operation_to_top_pool( + top_pool_author.as_ref(), + &trusted_operation, + &shielding_key, + shard, + ) + .unwrap(); + + // when + let mut execution_result = + execute_trusted_calls(&shard, stf_executor.as_ref(), &top_pool_author); + + let nonce = TestStf::get_account_nonce( + &mut execution_result.state_after_execution, + &sender.public().into(), + ); + assert_eq!(nonce, 1); +} + +fn test_call_set_update_parentchain_block() { + let (_, _, shard, _, _, state_handler, _) = test_setup(); + let mut state = state_handler.load(&shard).unwrap(); + + let block_number = 3; + let parent_hash = H256::from([1; 32]); + + let header: Header = HeaderT::new( + block_number, + Default::default(), + Default::default(), + parent_hash, + Default::default(), + ); + + TestStf::update_parentchain_block(&mut state, header.clone()).unwrap(); + + assert_eq!(header.hash(), state.execute_with(|| Parentchain::block_hash())); + assert_eq!(parent_hash, state.execute_with(|| Parentchain::parent_hash())); + assert_eq!(block_number, state.execute_with(|| Parentchain::block_number())); +} + +fn test_signature_must_match_public_sender_in_call() { + // given + let (top_pool_author, _, shard, mrenclave, shielding_key, _, stf_executor) = test_setup(); + + // create accounts + let sender = funded_pair(); + let receiver = unfunded_public(); + + let trusted_operation = + TrustedCall::balance_transfer(receiver.into(), sender.public().into(), 1000) + .sign(&sender.clone().into(), 10, &mrenclave, &shard) + .into_trusted_operation(true); + + submit_operation_to_top_pool( + top_pool_author.as_ref(), + &trusted_operation, + &shielding_key, + shard, + ) + .unwrap(); + + // when + let executed_batch = execute_trusted_calls(&shard, stf_executor.as_ref(), &top_pool_author); + + // then + assert!(!executed_batch.executed_operations[0].is_success()); +} + +fn test_invalid_nonce_call_is_not_executed() { + // given + let (top_pool_author, _, shard, mrenclave, shielding_key, _, stf_executor) = test_setup(); + + // create accounts + let sender = funded_pair(); + let receiver = unfunded_public(); + + let trusted_operation = + TrustedCall::balance_transfer(sender.public().into(), receiver.into(), 1000) + .sign(&sender.clone().into(), 10, &mrenclave, &shard) + .into_trusted_operation(true); + + submit_operation_to_top_pool( + top_pool_author.as_ref(), + &trusted_operation, + &shielding_key, + shard, + ) + .unwrap(); + + // when + let executed_batch = execute_trusted_calls(&shard, stf_executor.as_ref(), &top_pool_author); + + // then + assert!(!executed_batch.executed_operations[0].is_success()); +} + +fn test_non_root_shielding_call_is_not_executed() { + // given + let (top_pool_author, _state, shard, mrenclave, shielding_key, _, stf_executor) = test_setup(); + + let sender = funded_pair(); + let sender_acc: AccountId = sender.public().into(); + + let signed_call = TrustedCall::balance_shield(sender_acc.clone(), sender_acc.clone(), 1000) + .sign(&sender.into(), 0, &mrenclave, &shard); + + submit_operation_to_top_pool( + top_pool_author.as_ref(), + &direct_top(signed_call), + &shielding_key, + shard, + ) + .unwrap(); + + // when + let executed_batch = execute_trusted_calls(&shard, stf_executor.as_ref(), &top_pool_author); + + // then + assert!(!executed_batch.executed_operations[0].is_success()); +} + +fn test_shielding_call_with_enclave_self_is_executed() { + let (top_pool_author, _state, shard, mrenclave, shielding_key, _, stf_executor) = test_setup(); + + let sender = funded_pair(); + let sender_account: AccountId = sender.public().into(); + let enclave_call_signer = enclave_call_signer(&shielding_key); + + let signed_call = TrustedCall::balance_shield( + enclave_call_signer.public().into(), + sender_account.clone(), + 1000, + ) + .sign(&enclave_call_signer.into(), 0, &mrenclave, &shard); + let trusted_operation = TrustedOperation::indirect_call(signed_call); + + submit_operation_to_top_pool( + top_pool_author.as_ref(), + &trusted_operation, + &shielding_key, + shard, + ) + .unwrap(); + + // when + let executed_batch = + execute_trusted_calls(&shard, stf_executor.as_ref(), top_pool_author.as_ref()); + + // then + assert_eq!(1, executed_batch.executed_operations.len()); + assert!(executed_batch.executed_operations[0].is_success()); +} + +pub fn test_retrieve_events() { + // given + let (_, mut state, shard, mrenclave, ..) = test_setup(); + let mut opaque_vec = Vec::new(); + let sender = funded_pair(); + let receiver = unendowed_account(); + let transfer_value: u128 = 1_000; + // Events will only get executed after genesis. + state.execute_with(|| set_block_number(100)); + + // Execute a transfer extrinsic to generate events via the Balance pallet. + let trusted_call = TrustedCall::balance_transfer( + sender.public().into(), + receiver.public().into(), + transfer_value, + ) + .sign(&sender.clone().into(), 0, &mrenclave, &shard); + let repo = Arc::new(NodeMetadataRepository::::default()); + let shard = ShardIdentifier::default(); + TestStf::execute_call(&mut state, &shard, trusted_call, &mut opaque_vec, repo).unwrap(); + + assert_eq!(TestStf::get_events(&mut state).len(), 3); +} + +pub fn test_retrieve_event_count() { + let (_, mut state, shard, mrenclave, ..) = test_setup(); + let mut opaque_vec = Vec::new(); + let sender = funded_pair(); + let receiver = unendowed_account(); + let transfer_value: u128 = 1_000; + // Events will only get executed after genesis. + state.execute_with(|| set_block_number(100)); + + // Execute a transfer extrinsic to generate events via the Balance pallet. + let trusted_call = TrustedCall::balance_transfer( + sender.public().into(), + receiver.public().into(), + transfer_value, + ) + .sign(&sender.clone().into(), 0, &mrenclave, &shard); + + // when + let repo = Arc::new(NodeMetadataRepository::::default()); + let shard = ShardIdentifier::default(); + TestStf::execute_call(&mut state, &shard, trusted_call, &mut opaque_vec, repo).unwrap(); + + let event_count = TestStf::get_event_count(&mut state); + assert_eq!(event_count, 3); +} + +pub fn test_reset_events() { + let (_, mut state, shard, mrenclave, ..) = test_setup(); + let mut opaque_vec = Vec::new(); + let sender = funded_pair(); + let receiver = unendowed_account(); + let transfer_value: u128 = 1_000; + // Events will only get executed after genesis. + state.execute_with(|| set_block_number(100)); + // Execute a transfer extrinsic to generate events via the Balance pallet. + let trusted_call = TrustedCall::balance_transfer( + sender.public().into(), + receiver.public().into(), + transfer_value, + ) + .sign(&sender.clone().into(), 0, &mrenclave, &shard); + let repo = Arc::new(NodeMetadataRepository::::default()); + let shard = ShardIdentifier::default(); + TestStf::execute_call(&mut state, &shard, trusted_call, &mut opaque_vec, repo).unwrap(); + let receiver_acc_info = TestStf::get_account_data(&mut state, &receiver.public().into()); + assert_eq!(receiver_acc_info.free, transfer_value); + // Ensure that there really have been events generated. + assert_eq!(TestStf::get_events(&mut state).len(), 3); + + // Remove the events. + TestStf::reset_events(&mut state); + + // Ensure that the events storage has been cleared. + assert_eq!(TestStf::get_events(&mut state).len(), 0); +} + +fn execute_trusted_calls( + shard: &ShardIdentifier, + stf_executor: &TestStfExecutor, + top_pool_author: &TestTopPoolAuthor, +) -> BatchExecutionResult { + let top_pool_calls = top_pool_author.get_pending_trusted_calls(*shard); + let execution_result = stf_executor + .propose_state_update( + &top_pool_calls, + &latest_parentchain_header(), + &shard, + Duration::from_millis(600), + |s| { + let mut sidechain_db = SidechainDB::::new(s); + sidechain_db + .set_block_number(&sidechain_db.get_block_number().map_or(1, |n| n + 1)); + sidechain_db.ext + }, + ) + .unwrap(); + execution_result +} + +// helper functions +/// Decrypt `encrypted` and decode it into `StatePayload` +pub fn encrypted_state_diff_from_encrypted( + encrypted: &[u8], +) -> StatePayload { + let mut encrypted_payload: Vec = encrypted.to_vec(); + let state_key = state_key(); + state_key.decrypt(&mut encrypted_payload).unwrap(); + StatePayload::decode(&mut encrypted_payload.as_slice()).unwrap() +} + +pub fn state_key() -> Aes { + Aes::default() +} + +/// Some random account that has no funds in the `Stf`'s `test_genesis` config. +pub fn unfunded_public() -> spEd25519::Public { + spEd25519::Public::from_raw(*b"asdfasdfadsfasdfasfasdadfadfasdf") +} + +pub fn test_account() -> spEd25519::Pair { + spEd25519::Pair::from_seed(b"42315678901234567890123456789012") +} + +/// transforms `call` into `TrustedOperation::direct(call)` +pub fn direct_top(call: TrustedCallSigned) -> TrustedOperation { + call.into_trusted_operation(true) +} + +/// Just some random onchain header +pub fn latest_parentchain_header() -> Header { + Header::new(1, Default::default(), Default::default(), [69; 32].into(), Default::default()) +} + +/// Reads the value at `key_hash` from `state_diff` and decodes it into `D` +pub fn get_from_state_diff(state_diff: &SgxExternalitiesDiffType, key_hash: &[u8]) -> D { + // fixme: what's up here with the wrapping?? + state_diff + .get(key_hash) + .unwrap() + .as_ref() + .map(|d| Decode::decode(&mut d.as_slice())) + .unwrap() + .unwrap() +} diff --git a/tee-worker/enclave-runtime/src/test/top_pool_tests.rs b/tee-worker/enclave-runtime/src/test/top_pool_tests.rs new file mode 100644 index 0000000000..ea03fc782c --- /dev/null +++ b/tee-worker/enclave-runtime/src/test/top_pool_tests.rs @@ -0,0 +1,203 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + Copyright (C) 2017-2019 Baidu, Inc. All Rights Reserved. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +use crate::test::{ + fixtures::{ + components::{ + create_ocall_api, create_top_pool, encrypt_trusted_operation, sign_trusted_call, + }, + initialize_test_state::init_state, + test_setup::TestStf, + }, + mocks::types::{ + TestShieldingKey, TestShieldingKeyRepo, TestSigner, TestStateHandler, TestTopPoolAuthor, + }, +}; +use codec::Encode; +use ita_stf::{ + test_genesis::{endowed_account, unendowed_account}, + TrustedCall, TrustedOperation, +}; +use itc_parentchain::indirect_calls_executor::{ExecuteIndirectCalls, IndirectCallsExecutor}; +use itc_parentchain_test::{ + parentchain_block_builder::ParentchainBlockBuilder, + parentchain_header_builder::ParentchainHeaderBuilder, +}; +use itp_node_api::{ + api_client::{ + ParentchainExtrinsicParams, ParentchainExtrinsicParamsBuilder, + ParentchainUncheckedExtrinsic, + }, + metadata::{ + metadata_mocks::NodeMetadataMock, pallet_teerex::TeerexCallIndexes, + provider::NodeMetadataRepository, + }, +}; +use itp_ocall_api::EnclaveAttestationOCallApi; +use itp_sgx_crypto::ShieldingCryptoEncrypt; +use itp_stf_executor::enclave_signer::StfEnclaveSigner; +use itp_stf_state_observer::mock::ObserveStateMock; +use itp_test::mock::metrics_ocall_mock::MetricsOCallMock; +use itp_top_pool_author::{top_filter::AllowAllTopsFilter, traits::AuthorApi}; +use itp_types::{AccountId, Block, ShardIdentifier, ShieldFundsFn, H256}; +use jsonrpc_core::futures::executor; +use log::*; +use sgx_crypto_helper::RsaKeyPair; +use sp_core::{ed25519, Pair}; +use sp_runtime::{MultiSignature, OpaqueExtrinsic}; +use std::{sync::Arc, vec::Vec}; +use substrate_api_client::{ExtrinsicParams, GenericAddress}; + +pub fn process_indirect_call_in_top_pool() { + let _ = env_logger::builder().is_test(true).try_init(); + info!("Setting up test."); + + let signer = TestSigner::from_seed(b"42315678901234567890123456789012"); + let shielding_key = TestShieldingKey::new().unwrap(); + let shielding_key_repo = Arc::new(TestShieldingKeyRepo::new(shielding_key)); + let header = ParentchainHeaderBuilder::default().build(); + + let ocall_api = create_ocall_api(&header, &signer); + + let state_handler = Arc::new(TestStateHandler::default()); + let (_, shard_id) = init_state(state_handler.as_ref(), signer.public().into()); + + let top_pool = create_top_pool(); + + let top_pool_author = Arc::new(TestTopPoolAuthor::new( + top_pool, + AllowAllTopsFilter {}, + state_handler.clone(), + shielding_key_repo, + Arc::new(MetricsOCallMock::default()), + )); + + let encrypted_indirect_call = + encrypted_indirect_call(ocall_api.as_ref(), &shard_id, &shielding_key); + + executor::block_on(top_pool_author.submit_top(encrypted_indirect_call, shard_id)).unwrap(); + + assert_eq!(1, top_pool_author.get_pending_trusted_calls(shard_id).len()); +} + +pub fn submit_shielding_call_to_top_pool() { + let _ = env_logger::builder().is_test(true).try_init(); + + let signer = TestSigner::from_seed(b"42315678901234567890123456789012"); + let shielding_key = TestShieldingKey::new().unwrap(); + let shielding_key_repo = Arc::new(TestShieldingKeyRepo::new(shielding_key.clone())); + let header = ParentchainHeaderBuilder::default().build(); + + let ocall_api = create_ocall_api(&header, &signer); + let mr_enclave = ocall_api.get_mrenclave_of_self().unwrap(); + + let state_handler = Arc::new(TestStateHandler::default()); + let (state, shard_id) = init_state(state_handler.as_ref(), signer.public().into()); + let state_observer = Arc::new(ObserveStateMock::new(state)); + + let top_pool = create_top_pool(); + + let top_pool_author = Arc::new(TestTopPoolAuthor::new( + top_pool, + AllowAllTopsFilter {}, + state_handler, + shielding_key_repo.clone(), + Arc::new(MetricsOCallMock::default()), + )); + + let enclave_signer = Arc::new(StfEnclaveSigner::<_, _, _, TestStf, _>::new( + state_observer, + ocall_api.clone(), + shielding_key_repo.clone(), + top_pool_author.clone(), + )); + let node_meta_data_repository = Arc::new(NodeMetadataRepository::default()); + node_meta_data_repository.set_metadata(NodeMetadataMock::new()); + let indirect_calls_executor = IndirectCallsExecutor::new( + shielding_key_repo, + enclave_signer, + top_pool_author.clone(), + node_meta_data_repository, + ); + + let block_with_shielding_call = create_shielding_call_extrinsic(shard_id, &shielding_key); + + let _ = indirect_calls_executor + .execute_indirect_calls_in_extrinsics(&block_with_shielding_call) + .unwrap(); + + assert_eq!(1, top_pool_author.get_pending_trusted_calls(shard_id).len()); + let trusted_operation = + top_pool_author.get_pending_trusted_calls(shard_id).first().cloned().unwrap(); + let trusted_call = trusted_operation.to_call().unwrap(); + assert!(trusted_call.verify_signature(&mr_enclave.m, &shard_id)); +} + +fn encrypted_indirect_call< + AttestationApi: EnclaveAttestationOCallApi, + ShieldingKey: ShieldingCryptoEncrypt, +>( + attestation_api: &AttestationApi, + shard_id: &ShardIdentifier, + shielding_key: &ShieldingKey, +) -> Vec { + let sender = endowed_account(); + let receiver = unendowed_account(); + + let call = + TrustedCall::balance_transfer(sender.public().into(), receiver.public().into(), 10000u128); + let call_signed = sign_trusted_call(&call, attestation_api, shard_id, sender); + let trusted_operation = TrustedOperation::indirect_call(call_signed); + encrypt_trusted_operation(shielding_key, &trusted_operation) +} + +fn create_shielding_call_extrinsic( + shard: ShardIdentifier, + shielding_key: &ShieldingKey, +) -> Block { + let target_account = shielding_key.encrypt(&AccountId::new([2u8; 32]).encode()).unwrap(); + let test_signer = ed25519::Pair::from_seed(b"33345678901234567890123456789012"); + let signature = test_signer.sign(&[0u8]); + + let default_extra_for_test = ParentchainExtrinsicParams::new( + 0, + 0, + 0, + H256::default(), + ParentchainExtrinsicParamsBuilder::default(), + ); + + let dummy_node_metadata = NodeMetadataMock::new(); + + let shield_funds_indexes = dummy_node_metadata.shield_funds_call_indexes().unwrap(); + let opaque_extrinsic = OpaqueExtrinsic::from_bytes( + ParentchainUncheckedExtrinsic::::new_signed( + (shield_funds_indexes, target_account, 1000u128, shard), + GenericAddress::Address32([1u8; 32]), + MultiSignature::Ed25519(signature), + default_extra_for_test.signed_extra(), + ) + .encode() + .as_slice(), + ) + .unwrap(); + + ParentchainBlockBuilder::default() + .with_extrinsics(vec![opaque_extrinsic]) + .build() +} diff --git a/tee-worker/enclave-runtime/src/tls_ra/authentication.rs b/tee-worker/enclave-runtime/src/tls_ra/authentication.rs new file mode 100644 index 0000000000..3cae981bac --- /dev/null +++ b/tee-worker/enclave-runtime/src/tls_ra/authentication.rs @@ -0,0 +1,118 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +//! Remote attestation certificate authentication of server and client + +use itp_attestation_handler::cert; +use itp_ocall_api::EnclaveAttestationOCallApi; +use log::*; +use sgx_types::*; +use webpki::DNSName; + +pub struct ClientAuth
{ + outdated_ok: bool, + skip_ra: bool, + attestation_ocall: A, +} + +impl ClientAuth { + pub fn new(outdated_ok: bool, skip_ra: bool, attestation_ocall: A) -> Self { + ClientAuth { outdated_ok, skip_ra, attestation_ocall } + } +} + +impl rustls::ClientCertVerifier for ClientAuth +where + A: EnclaveAttestationOCallApi, +{ + fn client_auth_root_subjects( + &self, + _sni: Option<&DNSName>, + ) -> Option { + Some(rustls::DistinguishedNames::new()) + } + + fn verify_client_cert( + &self, + _certs: &[rustls::Certificate], + _sni: Option<&DNSName>, + ) -> Result { + debug!("client cert: {:?}", _certs); + // This call will automatically verify cert is properly signed + if self.skip_ra { + warn!("Skip verifying ra-report"); + return Ok(rustls::ClientCertVerified::assertion()) + } + + match cert::verify_mra_cert(&_certs[0].0, &self.attestation_ocall) { + Ok(()) => Ok(rustls::ClientCertVerified::assertion()), + Err(sgx_status_t::SGX_ERROR_UPDATE_NEEDED) => + if self.outdated_ok { + warn!("outdated_ok is set, overriding outdated error"); + Ok(rustls::ClientCertVerified::assertion()) + } else { + Err(rustls::TLSError::WebPKIError(webpki::Error::ExtensionValueInvalid)) + }, + Err(_) => Err(rustls::TLSError::WebPKIError(webpki::Error::ExtensionValueInvalid)), + } + } +} + +pub struct ServerAuth { + outdated_ok: bool, + skip_ra: bool, + attestation_ocall: A, +} + +impl ServerAuth { + pub fn new(outdated_ok: bool, skip_ra: bool, attestation_ocall: A) -> Self { + ServerAuth { outdated_ok, skip_ra, attestation_ocall } + } +} + +impl rustls::ServerCertVerifier for ServerAuth +where + A: EnclaveAttestationOCallApi, +{ + fn verify_server_cert( + &self, + _roots: &rustls::RootCertStore, + certs: &[rustls::Certificate], + _hostname: webpki::DNSNameRef, + _ocsp: &[u8], + ) -> Result { + debug!("server cert: {:?}", certs); + + if self.skip_ra { + warn!("Skip verifying ra-report"); + return Ok(rustls::ServerCertVerified::assertion()) + } + + // This call will automatically verify cert is properly signed + match cert::verify_mra_cert(&certs[0].0, &self.attestation_ocall) { + Ok(()) => Ok(rustls::ServerCertVerified::assertion()), + Err(sgx_status_t::SGX_ERROR_UPDATE_NEEDED) => + if self.outdated_ok { + warn!("outdated_ok is set, overriding outdated error"); + Ok(rustls::ServerCertVerified::assertion()) + } else { + Err(rustls::TLSError::WebPKIError(webpki::Error::ExtensionValueInvalid)) + }, + Err(_) => Err(rustls::TLSError::WebPKIError(webpki::Error::ExtensionValueInvalid)), + } + } +} diff --git a/tee-worker/enclave-runtime/src/tls_ra/mocks.rs b/tee-worker/enclave-runtime/src/tls_ra/mocks.rs new file mode 100644 index 0000000000..2a918f48e0 --- /dev/null +++ b/tee-worker/enclave-runtime/src/tls_ra/mocks.rs @@ -0,0 +1,76 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +use super::seal_handler::{SealStateAndKeys, UnsealStateAndKeys}; +use crate::error::Result as EnclaveResult; +use itp_types::ShardIdentifier; +use std::{ + sync::{Arc, SgxRwLock as RwLock}, + vec::Vec, +}; + +#[derive(Clone)] +pub struct SealHandlerMock { + pub shielding_key: Arc>>, + pub state_key: Arc>>, + pub state: Arc>>, +} + +impl SealHandlerMock { + pub fn new( + shielding_key: Arc>>, + state_key: Arc>>, + state: Arc>>, + ) -> Self { + Self { shielding_key, state_key, state } + } +} + +impl SealStateAndKeys for SealHandlerMock { + fn seal_shielding_key(&self, bytes: &[u8]) -> EnclaveResult<()> { + *self.shielding_key.write().unwrap() = bytes.to_vec(); + Ok(()) + } + + fn seal_state_key(&self, bytes: &[u8]) -> EnclaveResult<()> { + *self.state_key.write().unwrap() = bytes.to_vec(); + Ok(()) + } + + fn seal_state(&self, bytes: &[u8], _shard: &ShardIdentifier) -> EnclaveResult<()> { + *self.state.write().unwrap() = bytes.to_vec(); + Ok(()) + } + + fn seal_new_empty_state(&self, _shard: &ShardIdentifier) -> EnclaveResult<()> { + Ok(()) + } +} + +impl UnsealStateAndKeys for SealHandlerMock { + fn unseal_shielding_key(&self) -> EnclaveResult> { + Ok(self.shielding_key.read().unwrap().clone()) + } + + fn unseal_state_key(&self) -> EnclaveResult> { + Ok(self.state_key.read().unwrap().clone()) + } + + fn unseal_state(&self, _shard: &ShardIdentifier) -> EnclaveResult> { + Ok(self.state.read().unwrap().clone()) + } +} diff --git a/tee-worker/enclave-runtime/src/tls_ra/mod.rs b/tee-worker/enclave-runtime/src/tls_ra/mod.rs new file mode 100644 index 0000000000..8bb672b5f3 --- /dev/null +++ b/tee-worker/enclave-runtime/src/tls_ra/mod.rs @@ -0,0 +1,69 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +//! Contains all logic of the state provisioning mechanism +//! including the remote attestation and tls / tcp connection part. + +mod authentication; +pub mod seal_handler; +mod tls_ra_client; +mod tls_ra_server; + +#[cfg(feature = "test")] +pub mod tests; + +#[cfg(feature = "test")] +pub mod mocks; + +/// Header of an accompanied payloard. Indicates the +/// length an the type (opcode) of the following payload. +#[derive(Clone, Debug)] +pub struct TcpHeader { + pub opcode: Opcode, + pub payload_length: u64, +} + +impl TcpHeader { + fn new(opcode: Opcode, payload_length: u64) -> Self { + Self { opcode, payload_length } + } +} + +/// Indicates the payload content type. +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub enum Opcode { + ShieldingKey = 0, + StateKey = 1, + State = 2, +} + +impl From for Opcode { + fn from(item: u8) -> Self { + match item { + 0 => Opcode::ShieldingKey, + 1 => Opcode::StateKey, + 2 => Opcode::State, + _ => unimplemented!("Unsupported/unknown Opcode for MU-RA exchange"), + } + } +} + +impl Opcode { + pub fn to_bytes(self) -> [u8; 1] { + (self as u8).to_be_bytes() + } +} diff --git a/tee-worker/enclave-runtime/src/tls_ra/seal_handler.rs b/tee-worker/enclave-runtime/src/tls_ra/seal_handler.rs new file mode 100644 index 0000000000..b3f6f7bdd3 --- /dev/null +++ b/tee-worker/enclave-runtime/src/tls_ra/seal_handler.rs @@ -0,0 +1,255 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +//! Abstraction of the reading (unseal) and storing (seal) part of the +//! shielding key, state key and state. + +use crate::error::{Error as EnclaveError, Result as EnclaveResult}; +use codec::{Decode, Encode}; +use ita_stf::{State as StfState, StateType as StfStateType}; +use itp_sgx_crypto::{ + key_repository::{AccessKey, MutateKey}, + Aes, +}; +use itp_sgx_externalities::SgxExternalitiesTrait; +use itp_stf_state_handler::handle_state::HandleState; +use itp_types::ShardIdentifier; +use log::*; +use sgx_crypto_helper::rsa3072::Rsa3072KeyPair; +use std::{sync::Arc, vec::Vec}; + +/// Handles the sealing and unsealing of the shielding key, state key and the state. +#[derive(Default)] +pub struct SealHandler +where + ShieldingKeyRepository: AccessKey + MutateKey, + StateKeyRepository: AccessKey + MutateKey, + // Constraint StateT = StfState currently necessary because SgxExternalities Encode/Decode does not work. + // See https://github.com/integritee-network/sgx-runtime/issues/46. + StateHandler: HandleState, +{ + state_handler: Arc, + state_key_repository: Arc, + shielding_key_repository: Arc, +} + +impl + SealHandler +where + ShieldingKeyRepository: AccessKey + MutateKey, + StateKeyRepository: AccessKey + MutateKey, + StateHandler: HandleState, +{ + pub fn new( + state_handler: Arc, + state_key_repository: Arc, + shielding_key_repository: Arc, + ) -> Self { + Self { state_handler, state_key_repository, shielding_key_repository } + } +} + +pub trait SealStateAndKeys { + fn seal_shielding_key(&self, bytes: &[u8]) -> EnclaveResult<()>; + fn seal_state_key(&self, bytes: &[u8]) -> EnclaveResult<()>; + fn seal_state(&self, bytes: &[u8], shard: &ShardIdentifier) -> EnclaveResult<()>; + fn seal_new_empty_state(&self, shard: &ShardIdentifier) -> EnclaveResult<()>; +} + +pub trait UnsealStateAndKeys { + fn unseal_shielding_key(&self) -> EnclaveResult>; + fn unseal_state_key(&self) -> EnclaveResult>; + fn unseal_state(&self, shard: &ShardIdentifier) -> EnclaveResult>; +} + +impl SealStateAndKeys + for SealHandler +where + ShieldingKeyRepository: AccessKey + MutateKey, + StateKeyRepository: AccessKey + MutateKey, + StateHandler: HandleState, +{ + fn seal_shielding_key(&self, bytes: &[u8]) -> EnclaveResult<()> { + let key: Rsa3072KeyPair = serde_json::from_slice(bytes).map_err(|e| { + error!(" [Enclave] Received Invalid RSA key"); + EnclaveError::Other(e.into()) + })?; + self.shielding_key_repository.update_key(key)?; + info!("Successfully stored a new shielding key"); + Ok(()) + } + + fn seal_state_key(&self, mut bytes: &[u8]) -> EnclaveResult<()> { + let aes = Aes::decode(&mut bytes)?; + self.state_key_repository.update_key(aes)?; + info!("Successfully stored a new state key"); + Ok(()) + } + + fn seal_state(&self, mut bytes: &[u8], shard: &ShardIdentifier) -> EnclaveResult<()> { + let state = StfStateType::decode(&mut bytes)?; + let state_with_empty_diff = StfState::new(state); + + self.state_handler.reset(state_with_empty_diff, shard)?; + info!("Successfully updated shard {:?} with provisioned state", shard); + Ok(()) + } + + /// Seal an empty, newly initialized state. + /// + /// Requires the shielding key to be sealed and updated before calling this. + /// + /// Call this function in case we don't provision the state itself, only the shielding key. + /// Since the enclave signing account is derived from the shielding key, we need to + /// newly initialize the state with the updated shielding key. + fn seal_new_empty_state(&self, shard: &ShardIdentifier) -> EnclaveResult<()> { + self.state_handler.initialize_shard(*shard)?; + info!("Successfully reset state with new enclave account, for shard {:?}", shard); + Ok(()) + } +} + +impl UnsealStateAndKeys + for SealHandler +where + ShieldingKeyRepository: AccessKey + MutateKey, + StateKeyRepository: AccessKey + MutateKey, + StateHandler: HandleState, +{ + fn unseal_shielding_key(&self) -> EnclaveResult> { + let shielding_key = self + .shielding_key_repository + .retrieve_key() + .map_err(|e| EnclaveError::Other(format!("{:?}", e).into()))?; + serde_json::to_vec(&shielding_key).map_err(|e| EnclaveError::Other(e.into())) + } + + fn unseal_state_key(&self) -> EnclaveResult> { + self.state_key_repository + .retrieve_key() + .map(|k| k.encode()) + .map_err(|e| EnclaveError::Other(format!("{:?}", e).into())) + } + + fn unseal_state(&self, shard: &ShardIdentifier) -> EnclaveResult> { + let state = self.state_handler.load(shard)?; + Ok(state.state.encode()) + } +} + +#[cfg(feature = "test")] +pub mod test { + use super::*; + use itp_sgx_crypto::mocks::KeyRepositoryMock; + use itp_test::mock::handle_state_mock::HandleStateMock; + + type StateKeyRepositoryMock = KeyRepositoryMock; + type ShieldingKeyRepositoryMock = KeyRepositoryMock; + + type SealHandlerMock = + SealHandler; + + pub fn seal_shielding_key_works() { + let seal_handler = SealHandlerMock::default(); + let key_pair_in_bytes = serde_json::to_vec(&Rsa3072KeyPair::default()).unwrap(); + + let result = seal_handler.seal_shielding_key(&key_pair_in_bytes); + + assert!(result.is_ok()); + } + + pub fn seal_shielding_key_fails_for_invalid_key() { + let seal_handler = SealHandlerMock::default(); + + let result = seal_handler.seal_shielding_key(&[1, 2, 3]); + + assert!(result.is_err()); + } + + pub fn unseal_seal_shielding_key_works() { + let seal_handler = SealHandlerMock::default(); + + let key_pair_in_bytes = seal_handler.unseal_shielding_key().unwrap(); + + let result = seal_handler.seal_shielding_key(&key_pair_in_bytes); + + assert!(result.is_ok()); + } + + pub fn seal_state_key_works() { + let seal_handler = SealHandlerMock::default(); + let key_pair_in_bytes = Aes::default().encode(); + + let result = seal_handler.seal_state_key(&key_pair_in_bytes); + + assert!(result.is_ok()); + } + + pub fn seal_state_key_fails_for_invalid_key() { + let seal_handler = SealHandlerMock::default(); + + let result = seal_handler.seal_state_key(&[1, 2, 3]); + + assert!(result.is_err()); + } + + pub fn unseal_seal_state_key_works() { + let seal_handler = SealHandlerMock::default(); + let key_pair_in_bytes = seal_handler.unseal_state_key().unwrap(); + + let result = seal_handler.seal_state_key(&key_pair_in_bytes); + + assert!(result.is_ok()); + } + + pub fn seal_state_works() { + let seal_handler = SealHandlerMock::default(); + let state = ::StateT::default(); + let shard = ShardIdentifier::default(); + let _init_hash = seal_handler.state_handler.initialize_shard(shard).unwrap(); + + let result = seal_handler.seal_state(&state.encode(), &shard); + + assert!(result.is_ok()); + } + + pub fn seal_state_fails_for_invalid_state() { + let seal_handler = SealHandlerMock::default(); + let shard = ShardIdentifier::default(); + + let result = seal_handler.seal_state(&[1, 0, 3], &shard); + + assert!(result.is_err()); + } + + pub fn unseal_seal_state_works() { + let seal_handler = SealHandlerMock::default(); + let shard = ShardIdentifier::default(); + seal_handler.state_handler.initialize_shard(shard).unwrap(); + // Fill our mock state: + let (lock, mut state) = seal_handler.state_handler.load_for_mutation(&shard).unwrap(); + let (key, value) = ("my_key", "my_value"); + state.insert(key.encode(), value.encode()); + seal_handler.state_handler.write_after_mutation(state, lock, &shard).unwrap(); + + let state_in_bytes = seal_handler.unseal_state(&shard).unwrap(); + + let result = seal_handler.seal_state(&state_in_bytes, &shard); + + assert!(result.is_ok()); + } +} diff --git a/tee-worker/enclave-runtime/src/tls_ra/tests.rs b/tee-worker/enclave-runtime/src/tls_ra/tests.rs new file mode 100644 index 0000000000..89cad53592 --- /dev/null +++ b/tee-worker/enclave-runtime/src/tls_ra/tests.rs @@ -0,0 +1,172 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +//! Tests of tls-ra client / server communication. + +use super::{ + mocks::SealHandlerMock, tls_ra_client::request_state_provisioning_internal, + tls_ra_server::run_state_provisioning_server_internal, +}; +use crate::{ + initialization::global_components::EnclaveStf, + tls_ra::seal_handler::{SealHandler, SealStateAndKeys, UnsealStateAndKeys}, +}; +use ita_stf::{AccountId, State}; +use itp_settings::worker_mode::{ProvideWorkerMode, WorkerMode, WorkerModeProvider}; +use itp_sgx_crypto::{mocks::KeyRepositoryMock, Aes}; +use itp_stf_interface::InitState; +use itp_stf_state_handler::handle_state::HandleState; +use itp_test::mock::handle_state_mock::HandleStateMock; +use itp_types::ShardIdentifier; +use sgx_crypto_helper::{rsa3072::Rsa3072KeyPair, RsaKeyPair}; +use sgx_types::sgx_quote_sign_type_t; +use std::{ + net::{TcpListener, TcpStream}, + os::unix::io::AsRawFd, + string::String, + sync::{Arc, SgxRwLock as RwLock}, + thread, + time::Duration, + vec::Vec, +}; + +static SIGN_TYPE: sgx_quote_sign_type_t = sgx_quote_sign_type_t::SGX_UNLINKABLE_SIGNATURE; +static SKIP_RA: i32 = 1; + +fn run_state_provisioning_server(seal_handler: impl UnsealStateAndKeys, port: u16) { + let listener = TcpListener::bind(server_addr(port)).unwrap(); + + let (socket, _addr) = listener.accept().unwrap(); + run_state_provisioning_server_internal::<_, WorkerModeProvider>( + socket.as_raw_fd(), + SIGN_TYPE, + SKIP_RA, + seal_handler, + ) + .unwrap(); +} + +fn server_addr(port: u16) -> String { + format!("127.0.0.1:{}", port) +} + +pub fn test_tls_ra_server_client_networking() { + let shard = ShardIdentifier::default(); + let shielding_key_encoded = vec![1, 2, 3]; + let state_key_encoded = vec![5, 2, 3, 7]; + let state_encoded = Vec::from([1u8; 26000]); // Have a decently sized state, so read() must be called multiple times. + + let server_seal_handler = SealHandlerMock::new( + Arc::new(RwLock::new(shielding_key_encoded.clone())), + Arc::new(RwLock::new(state_key_encoded.clone())), + Arc::new(RwLock::new(state_encoded.clone())), + ); + let initial_client_state = vec![0, 0, 1]; + let initial_client_state_key = vec![0, 0, 2]; + let client_shielding_key = Arc::new(RwLock::new(Vec::new())); + let client_state_key = Arc::new(RwLock::new(initial_client_state_key.clone())); + let client_state = Arc::new(RwLock::new(initial_client_state.clone())); + + let client_seal_handler = SealHandlerMock::new( + client_shielding_key.clone(), + client_state_key.clone(), + client_state.clone(), + ); + + let port: u16 = 3149; + + // Start server. + let server_thread_handle = thread::spawn(move || { + run_state_provisioning_server(server_seal_handler, port); + }); + thread::sleep(Duration::from_secs(1)); + + // Start client. + let socket = TcpStream::connect(server_addr(port)).unwrap(); + let result = request_state_provisioning_internal( + socket.as_raw_fd(), + SIGN_TYPE, + shard, + SKIP_RA, + client_seal_handler.clone(), + ); + + // Ensure server thread has finished. + server_thread_handle.join().unwrap(); + + assert!(result.is_ok()); + assert_eq!(*client_shielding_key.read().unwrap(), shielding_key_encoded); + + // State and state-key are provisioned only in sidechain mode + if WorkerModeProvider::worker_mode() == WorkerMode::Sidechain { + assert_eq!(*client_state.read().unwrap(), state_encoded); + assert_eq!(*client_state_key.read().unwrap(), state_key_encoded); + } else { + assert_eq!(*client_state.read().unwrap(), initial_client_state); + assert_eq!(*client_state_key.read().unwrap(), initial_client_state_key); + } +} + +// Test state and key provisioning with 'real' data structures. +pub fn test_state_and_key_provisioning() { + let state_key = Aes::new([3u8; 16], [0u8; 16]); + let shielding_key = Rsa3072KeyPair::new().unwrap(); + let initialized_state = EnclaveStf::init_state(AccountId::new([1u8; 32])); + let shard = ShardIdentifier::from([1u8; 32]); + + let server_seal_handler = + create_seal_handler(state_key, shielding_key, initialized_state, &shard); + let client_seal_handler = + create_seal_handler(Aes::default(), Rsa3072KeyPair::default(), State::default(), &shard); + + let port: u16 = 3150; + + // Start server. + let server_thread_handle = thread::spawn(move || { + run_state_provisioning_server(server_seal_handler, port); + }); + thread::sleep(Duration::from_secs(1)); + + // Start client. + let socket = TcpStream::connect(server_addr(port)).unwrap(); + let result = request_state_provisioning_internal( + socket.as_raw_fd(), + SIGN_TYPE, + shard, + SKIP_RA, + client_seal_handler, + ); + + // Ensure server thread has finished. + server_thread_handle.join().unwrap(); + + assert!(result.is_ok()); +} + +fn create_seal_handler( + state_key: Aes, + shielding_key: Rsa3072KeyPair, + state: State, + shard: &ShardIdentifier, +) -> impl UnsealStateAndKeys + SealStateAndKeys { + let state_key_repository = Arc::new(KeyRepositoryMock::::new(state_key)); + let shielding_key_repository = + Arc::new(KeyRepositoryMock::::new(shielding_key)); + let state_handler = Arc::new(HandleStateMock::default()); + state_handler.reset(state, shard).unwrap(); + SealHandler::new(state_handler, state_key_repository, shielding_key_repository) +} diff --git a/tee-worker/enclave-runtime/src/tls_ra/tls_ra_client.rs b/tee-worker/enclave-runtime/src/tls_ra/tls_ra_client.rs new file mode 100644 index 0000000000..1b7ca3bca8 --- /dev/null +++ b/tee-worker/enclave-runtime/src/tls_ra/tls_ra_client.rs @@ -0,0 +1,251 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +//! Implementation of the client part of the state provisioning. + +use super::{authentication::ServerAuth, Opcode, TcpHeader}; +use crate::{ + attestation::create_ra_report_and_signature, + error::{Error as EnclaveError, Result as EnclaveResult}, + initialization::global_components::{ + EnclaveSealHandler, GLOBAL_SHIELDING_KEY_REPOSITORY_COMPONENT, + GLOBAL_STATE_KEY_REPOSITORY_COMPONENT, + }, + ocall::OcallApi, + tls_ra::seal_handler::SealStateAndKeys, + GLOBAL_STATE_HANDLER_COMPONENT, +}; +use itp_attestation_handler::DEV_HOSTNAME; +use itp_component_container::ComponentGetter; +use itp_ocall_api::EnclaveAttestationOCallApi; +use itp_types::ShardIdentifier; +use log::*; +use rustls::{ClientConfig, ClientSession, Stream}; +use sgx_types::*; +use std::{ + backtrace::{self, PrintFormat}, + io::{Read, Write}, + net::TcpStream, + slice, + sync::Arc, + vec::Vec, +}; + +/// Client part of the TCP-level connection and the underlying TLS-level session. +/// +/// Includes a seal handler, which handles the storage part of the received data. +struct TlsClient<'a, StateAndKeySealer> +where + StateAndKeySealer: SealStateAndKeys, +{ + tls_stream: Stream<'a, ClientSession, TcpStream>, + seal_handler: StateAndKeySealer, + shard: ShardIdentifier, +} + +impl<'a, StateAndKeySealer> TlsClient<'a, StateAndKeySealer> +where + StateAndKeySealer: SealStateAndKeys, +{ + fn new( + tls_stream: Stream<'a, ClientSession, TcpStream>, + seal_handler: StateAndKeySealer, + shard: ShardIdentifier, + ) -> TlsClient { + TlsClient { tls_stream, seal_handler, shard } + } + + /// Read all data sent by the server of the specific shard. + /// + /// We trust here that the server sends us the correct data, as + /// we do not have any way to test it. + fn read_shard(&mut self) -> EnclaveResult<()> { + self.write_shard()?; + self.read_and_seal_all() + } + + /// Send the shard of the state we want to receive to the provisioning server. + fn write_shard(&mut self) -> EnclaveResult<()> { + self.tls_stream.write_all(self.shard.as_bytes())?; + Ok(()) + } + + /// Read and seal all relevant data sent by the server. + fn read_and_seal_all(&mut self) -> EnclaveResult<()> { + let mut received_payloads: Vec = Vec::new(); + + loop { + let maybe_opcode = self.read_and_seal()?; + match maybe_opcode { + None => break, + Some(o) => { + received_payloads.push(o); + }, + } + } + info!("Successfully read and sealed all data sent by the state provisioning server."); + + // In case we receive a shielding key, but no state, we need to reset our state + // to update the enclave account. + if received_payloads.contains(&Opcode::ShieldingKey) + && !received_payloads.contains(&Opcode::State) + { + self.seal_handler.seal_new_empty_state(&self.shard)?; + } + + Ok(()) + } + + /// Read a server header / payload pair and directly seal the received data. + fn read_and_seal(&mut self) -> EnclaveResult> { + let mut start_byte = [0u8; 1]; + let read_size = self.tls_stream.read(&mut start_byte)?; + // If we're reading but there's no data: EOF. + if read_size == 0 { + return Ok(None) + } + let header = self.read_header(start_byte[0])?; + let bytes = self.read_until(header.payload_length as usize)?; + match header.opcode { + Opcode::ShieldingKey => self.seal_handler.seal_shielding_key(&bytes)?, + Opcode::StateKey => self.seal_handler.seal_state_key(&bytes)?, + Opcode::State => self.seal_handler.seal_state(&bytes, &self.shard)?, + }; + Ok(Some(header.opcode)) + } + + /// Reads the payload header, indicating the sent payload length and type. + fn read_header(&mut self, start_byte: u8) -> EnclaveResult { + debug!("Read first byte: {:?}", start_byte); + // The first sent byte indicates the payload type. + let opcode: Opcode = start_byte.into(); + debug!("Read header opcode: {:?}", opcode); + // The following bytes contain the payload length, which is a u64. + let mut payload_length_buffer = [0u8; std::mem::size_of::()]; + self.tls_stream.read_exact(&mut payload_length_buffer)?; + let payload_length = u64::from_be_bytes(payload_length_buffer); + debug!("Payload length of {:?}: {}", opcode, payload_length); + + Ok(TcpHeader::new(opcode, payload_length)) + } + + /// Read all bytes into a buffer of given length. + fn read_until(&mut self, length: usize) -> EnclaveResult> { + let mut bytes = vec![0u8; length]; + self.tls_stream.read_exact(&mut bytes)?; + Ok(bytes) + } +} + +#[no_mangle] +pub unsafe extern "C" fn request_state_provisioning( + socket_fd: c_int, + sign_type: sgx_quote_sign_type_t, + shard: *const u8, + shard_size: u32, + skip_ra: c_int, +) -> sgx_status_t { + let _ = backtrace::enable_backtrace("enclave.signed.so", PrintFormat::Short); + let shard = ShardIdentifier::from_slice(slice::from_raw_parts(shard, shard_size as usize)); + + let state_handler = match GLOBAL_STATE_HANDLER_COMPONENT.get() { + Ok(s) => s, + Err(e) => { + error!("{:?}", e); + return sgx_status_t::SGX_ERROR_UNEXPECTED + }, + }; + + let state_key_repository = match GLOBAL_STATE_KEY_REPOSITORY_COMPONENT.get() { + Ok(s) => s, + Err(e) => { + error!("{:?}", e); + return sgx_status_t::SGX_ERROR_UNEXPECTED + }, + }; + + let shielding_key_repository = match GLOBAL_SHIELDING_KEY_REPOSITORY_COMPONENT.get() { + Ok(s) => s, + Err(e) => { + error!("{:?}", e); + return sgx_status_t::SGX_ERROR_UNEXPECTED + }, + }; + + let seal_handler = + EnclaveSealHandler::new(state_handler, state_key_repository, shielding_key_repository); + + if let Err(e) = + request_state_provisioning_internal(socket_fd, sign_type, shard, skip_ra, seal_handler) + { + error!("Failed to sync state due to: {:?}", e); + return e.into() + }; + + sgx_status_t::SGX_SUCCESS +} + +/// Internal [`request_state_provisioning`] function to be able to use the handy `?` operator. +pub(crate) fn request_state_provisioning_internal( + socket_fd: c_int, + sign_type: sgx_quote_sign_type_t, + shard: ShardIdentifier, + skip_ra: c_int, + seal_handler: StateAndKeySealer, +) -> EnclaveResult<()> { + let client_config = tls_client_config(sign_type, OcallApi, skip_ra == 1)?; + let (mut client_session, mut tcp_stream) = tls_client_session_stream(socket_fd, client_config)?; + + let mut client = TlsClient::new( + rustls::Stream::new(&mut client_session, &mut tcp_stream), + seal_handler, + shard, + ); + + info!("Requesting keys and state from mu-ra server of fellow validateer"); + client.read_shard() +} + +fn tls_client_config( + sign_type: sgx_quote_sign_type_t, + ocall_api: A, + skip_ra: bool, +) -> EnclaveResult { + let (key_der, cert_der) = create_ra_report_and_signature(sign_type, skip_ra)?; + + let mut cfg = rustls::ClientConfig::new(); + let certs = vec![rustls::Certificate(cert_der)]; + let privkey = rustls::PrivateKey(key_der); + + cfg.set_single_client_cert(certs, privkey).unwrap(); + cfg.dangerous() + .set_certificate_verifier(Arc::new(ServerAuth::new(true, skip_ra, ocall_api))); + cfg.versions.clear(); + cfg.versions.push(rustls::ProtocolVersion::TLSv1_2); + Ok(cfg) +} + +fn tls_client_session_stream( + socket_fd: i32, + client_config: ClientConfig, +) -> EnclaveResult<(ClientSession, TcpStream)> { + let dns_name = webpki::DNSNameRef::try_from_ascii_str(DEV_HOSTNAME) + .map_err(|e| EnclaveError::Other(e.into()))?; + let sess = rustls::ClientSession::new(&Arc::new(client_config), dns_name); + let conn = TcpStream::new(socket_fd)?; + Ok((sess, conn)) +} diff --git a/tee-worker/enclave-runtime/src/tls_ra/tls_ra_server.rs b/tee-worker/enclave-runtime/src/tls_ra/tls_ra_server.rs new file mode 100644 index 0000000000..35a053814f --- /dev/null +++ b/tee-worker/enclave-runtime/src/tls_ra/tls_ra_server.rs @@ -0,0 +1,245 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +//! Implementation of the server part of the state provisioning. + +use super::{authentication::ClientAuth, Opcode, TcpHeader}; +use crate::{ + attestation::create_ra_report_and_signature, + error::{Error as EnclaveError, Result as EnclaveResult}, + initialization::global_components::{ + EnclaveSealHandler, GLOBAL_SHIELDING_KEY_REPOSITORY_COMPONENT, + GLOBAL_STATE_KEY_REPOSITORY_COMPONENT, + }, + ocall::OcallApi, + tls_ra::seal_handler::UnsealStateAndKeys, + GLOBAL_STATE_HANDLER_COMPONENT, +}; +use itp_component_container::ComponentGetter; +use itp_ocall_api::EnclaveAttestationOCallApi; +use itp_settings::worker_mode::{ProvideWorkerMode, WorkerMode, WorkerModeProvider}; +use itp_types::ShardIdentifier; +use log::*; +use rustls::{ServerConfig, ServerSession, StreamOwned}; +use sgx_types::*; +use std::{ + backtrace::{self, PrintFormat}, + io::{Read, Write}, + net::TcpStream, + sync::Arc, +}; + +#[derive(Clone, Eq, PartialEq, Debug)] +enum ProvisioningPayload { + Everything, + ShieldingKeyOnly, +} + +impl From for ProvisioningPayload { + fn from(m: WorkerMode) -> Self { + match m { + WorkerMode::OffChainWorker | WorkerMode::Teeracle => + ProvisioningPayload::ShieldingKeyOnly, + WorkerMode::Sidechain => ProvisioningPayload::Everything, + } + } +} + +/// Server part of the TCP-level connection and the underlying TLS-level session. +/// +/// Includes a seal handler, which handles the reading part of the data to be sent. +struct TlsServer { + tls_stream: StreamOwned, + seal_handler: StateAndKeyUnsealer, + provisioning_payload: ProvisioningPayload, +} + +impl TlsServer +where + StateAndKeyUnsealer: UnsealStateAndKeys, +{ + fn new( + tls_stream: StreamOwned, + seal_handler: StateAndKeyUnsealer, + provisioning_payload: ProvisioningPayload, + ) -> Self { + Self { tls_stream, seal_handler, provisioning_payload } + } + + /// Sends all relevant data of the specific shard to the client. + fn write_shard(&mut self) -> EnclaveResult<()> { + let shard = self.read_shard()?; + self.write_all(&shard) + } + + /// Read the shard of the state the client wants to receive. + fn read_shard(&mut self) -> EnclaveResult { + let mut shard_holder = ShardIdentifier::default(); + let shard = shard_holder.as_fixed_bytes_mut(); + self.tls_stream.read_exact(shard)?; + Ok(shard.into()) + } + + /// Sends all relevant data to the client. + fn write_all(&mut self, shard: &ShardIdentifier) -> EnclaveResult<()> { + debug!("Provisioning is set to: {:?}", self.provisioning_payload); + match self.provisioning_payload { + ProvisioningPayload::Everything => { + self.write_shielding_key()?; + self.write_state_key()?; + self.write_state(shard)?; + }, + ProvisioningPayload::ShieldingKeyOnly => { + self.write_shielding_key()?; + }, + } + + debug!("Successfully provisioned all payloads to peer"); + Ok(()) + } + + fn write_shielding_key(&mut self) -> EnclaveResult<()> { + let shielding_key = self.seal_handler.unseal_shielding_key()?; + self.write(Opcode::ShieldingKey, &shielding_key)?; + Ok(()) + } + + fn write_state_key(&mut self) -> EnclaveResult<()> { + let state_key = self.seal_handler.unseal_state_key()?; + self.write(Opcode::StateKey, &state_key)?; + Ok(()) + } + + fn write_state(&mut self, shard: &ShardIdentifier) -> EnclaveResult<()> { + let state = self.seal_handler.unseal_state(shard)?; + self.write(Opcode::State, &state)?; + Ok(()) + } + + /// Sends the header followed by the payload. + fn write(&mut self, opcode: Opcode, bytes: &[u8]) -> EnclaveResult<()> { + let payload_length = bytes.len() as u64; + self.write_header(TcpHeader::new(opcode, payload_length))?; + debug!("Write payload - opcode: {:?}, payload_length: {}", opcode, payload_length); + self.tls_stream.write_all(bytes)?; + Ok(()) + } + + /// Sends the header which includes the payload length and the Opcode indicating the payload type. + fn write_header(&mut self, tcp_header: TcpHeader) -> EnclaveResult<()> { + self.tls_stream.write_all(&tcp_header.opcode.to_bytes())?; + self.tls_stream.write_all(&tcp_header.payload_length.to_be_bytes())?; + debug!( + "Write header - opcode: {:?}, payload length: {}", + tcp_header.opcode, tcp_header.payload_length + ); + Ok(()) + } +} + +#[no_mangle] +pub unsafe extern "C" fn run_state_provisioning_server( + socket_fd: c_int, + sign_type: sgx_quote_sign_type_t, + skip_ra: c_int, +) -> sgx_status_t { + let _ = backtrace::enable_backtrace("enclave.signed.so", PrintFormat::Short); + + let state_handler = match GLOBAL_STATE_HANDLER_COMPONENT.get() { + Ok(s) => s, + Err(e) => { + error!("{:?}", e); + return sgx_status_t::SGX_ERROR_UNEXPECTED + }, + }; + + let state_key_repository = match GLOBAL_STATE_KEY_REPOSITORY_COMPONENT.get() { + Ok(s) => s, + Err(e) => { + error!("{:?}", e); + return sgx_status_t::SGX_ERROR_UNEXPECTED + }, + }; + + let shielding_key_repository = match GLOBAL_SHIELDING_KEY_REPOSITORY_COMPONENT.get() { + Ok(s) => s, + Err(e) => { + error!("{:?}", e); + return sgx_status_t::SGX_ERROR_UNEXPECTED + }, + }; + + let seal_handler = + EnclaveSealHandler::new(state_handler, state_key_repository, shielding_key_repository); + + if let Err(e) = run_state_provisioning_server_internal::<_, WorkerModeProvider>( + socket_fd, + sign_type, + skip_ra, + seal_handler, + ) { + error!("Failed to provision state due to: {:?}", e); + return e.into() + }; + + sgx_status_t::SGX_SUCCESS +} + +/// Internal [`run_state_provisioning_server`] function to be able to use the handy `?` operator. +pub(crate) fn run_state_provisioning_server_internal< + StateAndKeyUnsealer: UnsealStateAndKeys, + WorkerModeProvider: ProvideWorkerMode, +>( + socket_fd: c_int, + sign_type: sgx_quote_sign_type_t, + skip_ra: c_int, + seal_handler: StateAndKeyUnsealer, +) -> EnclaveResult<()> { + let server_config = tls_server_config(sign_type, OcallApi, skip_ra == 1)?; + let (server_session, tcp_stream) = tls_server_session_stream(socket_fd, server_config)?; + let provisioning = ProvisioningPayload::from(WorkerModeProvider::worker_mode()); + + let mut server = + TlsServer::new(StreamOwned::new(server_session, tcp_stream), seal_handler, provisioning); + + println!(" [Enclave] (MU-RA-Server) MU-RA successful sending keys"); + server.write_shard() +} + +fn tls_server_session_stream( + socket_fd: i32, + server_config: ServerConfig, +) -> EnclaveResult<(ServerSession, TcpStream)> { + let sess = ServerSession::new(&Arc::new(server_config)); + let conn = TcpStream::new(socket_fd).map_err(|e| EnclaveError::Other(e.into()))?; + Ok((sess, conn)) +} + +fn tls_server_config( + sign_type: sgx_quote_sign_type_t, + ocall_api: A, + skip_ra: bool, +) -> EnclaveResult { + let (key_der, cert_der) = create_ra_report_and_signature(sign_type, skip_ra)?; + + let mut cfg = rustls::ServerConfig::new(Arc::new(ClientAuth::new(true, skip_ra, ocall_api))); + let certs = vec![rustls::Certificate(cert_der)]; + let privkey = rustls::PrivateKey(key_der); + cfg.set_single_cert_with_ocsp_and_sct(certs, privkey, vec![], vec![]) + .map_err(|e| EnclaveError::Other(e.into()))?; + Ok(cfg) +} diff --git a/tee-worker/enclave-runtime/src/top_pool_execution.rs b/tee-worker/enclave-runtime/src/top_pool_execution.rs new file mode 100644 index 0000000000..d11fc4d8d7 --- /dev/null +++ b/tee-worker/enclave-runtime/src/top_pool_execution.rs @@ -0,0 +1,296 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +use crate::{ + error::Result, + initialization::global_components::{ + GLOBAL_OCALL_API_COMPONENT, GLOBAL_SIDECHAIN_BLOCK_COMPOSER_COMPONENT, + GLOBAL_SIDECHAIN_IMPORT_QUEUE_WORKER_COMPONENT, GLOBAL_STATE_HANDLER_COMPONENT, + GLOBAL_TOP_POOL_AUTHOR_COMPONENT, + }, + sync::{EnclaveLock, EnclaveStateRWLock}, + utils::{ + get_extrinsic_factory_from_solo_or_parachain, get_stf_executor_from_solo_or_parachain, + get_triggered_dispatcher_from_solo_or_parachain, + get_validator_accessor_from_solo_or_parachain, + }, +}; +use codec::Encode; +use itc_parentchain::{ + block_import_dispatcher::triggered_dispatcher::TriggerParentchainBlockImport, + light_client::{ + concurrent_access::ValidatorAccess, BlockNumberOps, ExtrinsicSender, LightClientState, + NumberFor, + }, +}; +use itp_component_container::ComponentGetter; +use itp_extrinsics_factory::CreateExtrinsics; +use itp_ocall_api::{EnclaveOnChainOCallApi, EnclaveSidechainOCallApi}; +use itp_settings::sidechain::SLOT_DURATION; +use itp_sgx_crypto::Ed25519Seal; +use itp_sgx_io::StaticSealedIO; +use itp_stf_state_handler::query_shard_state::QueryShardState; +use itp_time_utils::duration_now; +use itp_types::{Block, OpaqueCall, H256}; +use its_primitives::{ + traits::{ + Block as SidechainBlockTrait, Header as HeaderTrait, ShardIdentifierFor, SignedBlock, + }, + types::block::SignedBlock as SignedSidechainBlock, +}; +use its_sidechain::{ + aura::{proposer_factory::ProposerFactory, Aura, SlotClaimStrategy}, + consensus_common::{Environment, Error as ConsensusError, ProcessBlockImportQueue}, + slots::{sgx::LastSlotSeal, yield_next_slot, PerShardSlotWorkerScheduler, SlotInfo}, + validateer_fetch::ValidateerFetch, +}; +use log::*; +use sgx_types::sgx_status_t; +use sp_core::Pair; +use sp_runtime::{ + generic::SignedBlock as SignedParentchainBlock, traits::Block as BlockTrait, MultiSignature, +}; +use std::{sync::Arc, time::Instant, vec::Vec}; + +#[no_mangle] +pub unsafe extern "C" fn execute_trusted_calls() -> sgx_status_t { + if let Err(e) = execute_top_pool_trusted_calls_internal() { + return e.into() + } + + sgx_status_t::SGX_SUCCESS +} + +/// Internal [`execute_trusted_calls`] function to be able to use the `?` operator. +/// +/// Executes `Aura::on_slot() for `slot` if it is this enclave's `Slot`. +/// +/// This function makes an ocall that does the following: +/// +/// * Import all pending parentchain blocks. +/// * Sends sidechain `confirm_block` xt's with the produced sidechain blocks. +/// * Broadcast produced sidechain blocks to peer validateers. +fn execute_top_pool_trusted_calls_internal() -> Result<()> { + let start_time = Instant::now(); + + // We acquire lock explicitly (variable binding), since '_' will drop the lock after the statement. + // See https://medium.com/codechain/rust-underscore-does-not-bind-fec6a18115a8 + let _enclave_write_lock = EnclaveLock::write_all()?; + + let slot_beginning_timestamp = duration_now(); + + let parentchain_import_dispatcher = get_triggered_dispatcher_from_solo_or_parachain()?; + + let validator_access = get_validator_accessor_from_solo_or_parachain()?; + + // This gets the latest imported block. We accept that all of AURA, up until the block production + // itself, will operate on a parentchain block that is potentially outdated by one block + // (in case we have a block in the queue, but not imported yet). + let current_parentchain_header = validator_access.execute_on_validator(|v| { + let latest_parentchain_header = v.latest_finalized_header(v.num_relays())?; + Ok(latest_parentchain_header) + })?; + + // Import any sidechain blocks that are in the import queue. In case we are missing blocks, + // a peer sync will happen. If that happens, the slot time might already be used up just by this import. + let sidechain_block_import_queue_worker = + GLOBAL_SIDECHAIN_IMPORT_QUEUE_WORKER_COMPONENT.get()?; + + let latest_parentchain_header = + sidechain_block_import_queue_worker.process_queue(¤t_parentchain_header)?; + + info!( + "Elapsed time to process sidechain block import queue: {} ms", + start_time.elapsed().as_millis() + ); + + let stf_executor = get_stf_executor_from_solo_or_parachain()?; + + let top_pool_author = GLOBAL_TOP_POOL_AUTHOR_COMPONENT.get()?; + + let block_composer = GLOBAL_SIDECHAIN_BLOCK_COMPOSER_COMPONENT.get()?; + + let extrinsics_factory = get_extrinsic_factory_from_solo_or_parachain()?; + + let state_handler = GLOBAL_STATE_HANDLER_COMPONENT.get()?; + + let ocall_api = GLOBAL_OCALL_API_COMPONENT.get()?; + + let authority = Ed25519Seal::unseal_from_static_file()?; + + match yield_next_slot( + slot_beginning_timestamp, + SLOT_DURATION, + latest_parentchain_header, + &mut LastSlotSeal, + )? { + Some(slot) => { + if slot.duration_remaining().is_none() { + warn!("No time remaining in slot, skipping AURA execution"); + return Ok(()) + } + + log_remaining_slot_duration(&slot, "Before AURA"); + + let shards = state_handler.list_shards()?; + let env = ProposerFactory::::new( + top_pool_author, + stf_executor, + block_composer, + ); + + let (blocks, opaque_calls) = exec_aura_on_slot::<_, _, SignedSidechainBlock, _, _, _>( + slot.clone(), + authority, + ocall_api.clone(), + parentchain_import_dispatcher, + env, + shards, + )?; + + debug!("Aura executed successfully"); + + // Drop lock as soon as we don't need it anymore. + drop(_enclave_write_lock); + + log_remaining_slot_duration(&slot, "After AURA"); + + send_blocks_and_extrinsics::( + blocks, + opaque_calls, + ocall_api, + validator_access.as_ref(), + extrinsics_factory.as_ref(), + )?; + + log_remaining_slot_duration(&slot, "After broadcasting and sending extrinsic"); + }, + None => { + debug!("No slot yielded. Skipping block production."); + return Ok(()) + }, + }; + + debug!("End sidechain block production cycle"); + Ok(()) +} + +/// Executes aura for the given `slot`. +pub(crate) fn exec_aura_on_slot< + Authority, + ParentchainBlock, + SignedSidechainBlock, + OCallApi, + PEnvironment, + BlockImportTrigger, +>( + slot: SlotInfo, + authority: Authority, + ocall_api: Arc, + block_import_trigger: Arc, + proposer_environment: PEnvironment, + shards: Vec>, +) -> Result<(Vec, Vec)> +where + ParentchainBlock: BlockTrait, + SignedSidechainBlock: + SignedBlock + 'static, // Setting the public type is necessary due to some non-generic downstream code. + SignedSidechainBlock::Block: SidechainBlockTrait, + <::Block as SidechainBlockTrait>::HeaderType: + HeaderTrait, + SignedSidechainBlock::Signature: From, + Authority: Pair, + Authority::Public: Encode, + OCallApi: ValidateerFetch + EnclaveOnChainOCallApi + Send + 'static, + NumberFor: BlockNumberOps, + PEnvironment: + Environment + Send + Sync, + BlockImportTrigger: + TriggerParentchainBlockImport>, +{ + debug!("[Aura] Executing aura for slot: {:?}", slot); + + let mut aura = Aura::<_, ParentchainBlock, SignedSidechainBlock, PEnvironment, _, _>::new( + authority, + ocall_api.as_ref().clone(), + block_import_trigger, + proposer_environment, + ) + .with_claim_strategy(SlotClaimStrategy::RoundRobin); + + let (blocks, xts): (Vec<_>, Vec<_>) = + PerShardSlotWorkerScheduler::on_slot(&mut aura, slot, shards) + .into_iter() + .map(|r| (r.block, r.parentchain_effects)) + .unzip(); + + let opaque_calls: Vec = xts.into_iter().flatten().collect(); + Ok((blocks, opaque_calls)) +} + +/// Broadcasts sidechain blocks to fellow peers and sends opaque calls as extrinsic to the parentchain. +pub(crate) fn send_blocks_and_extrinsics< + ParentchainBlock, + SignedSidechainBlock, + OCallApi, + ValidatorAccessor, + ExtrinsicsFactory, +>( + blocks: Vec, + opaque_calls: Vec, + ocall_api: Arc, + validator_access: &ValidatorAccessor, + extrinsics_factory: &ExtrinsicsFactory, +) -> Result<()> +where + ParentchainBlock: BlockTrait, + SignedSidechainBlock: SignedBlock + 'static, + OCallApi: EnclaveSidechainOCallApi, + ValidatorAccessor: ValidatorAccess + Send + Sync + 'static, + NumberFor: BlockNumberOps, + ExtrinsicsFactory: CreateExtrinsics, +{ + debug!("Proposing {} sidechain block(s) (broadcasting to peers)", blocks.len()); + ocall_api.propose_sidechain_blocks(blocks)?; + + let xts = extrinsics_factory.create_extrinsics(opaque_calls.as_slice(), None)?; + + debug!("Sending sidechain block(s) confirmation extrinsic.. "); + validator_access.execute_mut_on_validator(|v| v.send_extrinsics(xts))?; + + Ok(()) +} + +fn log_remaining_slot_duration>( + slot_info: &SlotInfo, + stage_name: &str, +) { + match slot_info.duration_remaining() { + None => { + info!("No time remaining in slot (id: {:?}, stage: {})", slot_info.slot, stage_name); + }, + Some(remainder) => { + info!( + "Remaining time in slot (id: {:?}, stage {}): {} ms, {}% of slot time", + slot_info.slot, + stage_name, + remainder.as_millis(), + (remainder.as_millis() as f64 / slot_info.duration.as_millis() as f64) * 100f64 + ); + }, + }; +} diff --git a/tee-worker/enclave-runtime/src/utils.rs b/tee-worker/enclave-runtime/src/utils.rs new file mode 100644 index 0000000000..31310d783d --- /dev/null +++ b/tee-worker/enclave-runtime/src/utils.rs @@ -0,0 +1,143 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ +use crate::{ + error::{Error, Result}, + initialization::global_components::{ + EnclaveExtrinsicsFactory, EnclaveNodeMetadataRepository, + EnclaveParentchainBlockImportDispatcher, EnclaveStfExecutor, + EnclaveTriggeredParentchainBlockImportDispatcher, EnclaveValidatorAccessor, + GLOBAL_FULL_PARACHAIN_HANDLER_COMPONENT, GLOBAL_FULL_SOLOCHAIN_HANDLER_COMPONENT, + }, +}; +use codec::{Decode, Input}; +use itp_component_container::ComponentGetter; +use std::{result::Result as StdResult, slice, sync::Arc}; + +/// Helper trait to transform the sgx-ffi pointers to any type that implements +/// `parity-scale-codec::Decode` +pub unsafe trait DecodeRaw { + /// the type to decode into + type Decoded: Decode; + + unsafe fn decode_raw<'a, T>( + data: *const T, + len: usize, + ) -> StdResult + where + T: 'a, + &'a [T]: Input; +} + +unsafe impl DecodeRaw for D { + type Decoded = D; + + unsafe fn decode_raw<'a, T>( + data: *const T, + len: usize, + ) -> StdResult + where + T: 'a, + &'a [T]: Input, + { + let mut s = slice::from_raw_parts(data, len); + + Decode::decode(&mut s) + } +} + +pub unsafe fn utf8_str_from_raw<'a>( + data: *const u8, + len: usize, +) -> StdResult<&'a str, std::str::Utf8Error> { + let bytes = slice::from_raw_parts(data, len); + + std::str::from_utf8(bytes) +} + +// FIXME: When solving #1080, these helper functions should be obsolete, because no dynamic allocation +// is necessary anymore. +pub(crate) fn get_triggered_dispatcher_from_solo_or_parachain( +) -> Result> { + let dispatcher = if let Ok(solochain_handler) = GLOBAL_FULL_SOLOCHAIN_HANDLER_COMPONENT.get() { + get_triggered_dispatcher(solochain_handler.import_dispatcher.clone())? + } else if let Ok(parachain_handler) = GLOBAL_FULL_PARACHAIN_HANDLER_COMPONENT.get() { + get_triggered_dispatcher(parachain_handler.import_dispatcher.clone())? + } else { + return Err(Error::NoParentchainAssigned) + }; + Ok(dispatcher) +} + +pub(crate) fn get_triggered_dispatcher( + dispatcher: Arc, +) -> Result> { + let triggered_dispatcher = dispatcher + .triggered_dispatcher() + .ok_or(Error::ExpectedTriggeredImportDispatcher)?; + Ok(triggered_dispatcher) +} + +pub(crate) fn get_validator_accessor_from_solo_or_parachain( +) -> Result> { + let validator_accessor = + if let Ok(solochain_handler) = GLOBAL_FULL_SOLOCHAIN_HANDLER_COMPONENT.get() { + solochain_handler.validator_accessor.clone() + } else if let Ok(parachain_handler) = GLOBAL_FULL_PARACHAIN_HANDLER_COMPONENT.get() { + parachain_handler.validator_accessor.clone() + } else { + return Err(Error::NoParentchainAssigned) + }; + Ok(validator_accessor) +} + +pub(crate) fn get_node_metadata_repository_from_solo_or_parachain( +) -> Result> { + let metadata_repository = + if let Ok(solochain_handler) = GLOBAL_FULL_SOLOCHAIN_HANDLER_COMPONENT.get() { + solochain_handler.node_metadata_repository.clone() + } else if let Ok(parachain_handler) = GLOBAL_FULL_PARACHAIN_HANDLER_COMPONENT.get() { + parachain_handler.node_metadata_repository.clone() + } else { + return Err(Error::NoParentchainAssigned) + }; + Ok(metadata_repository) +} + +pub(crate) fn get_extrinsic_factory_from_solo_or_parachain() -> Result> +{ + let extrinsics_factory = + if let Ok(solochain_handler) = GLOBAL_FULL_SOLOCHAIN_HANDLER_COMPONENT.get() { + solochain_handler.extrinsics_factory.clone() + } else if let Ok(parachain_handler) = GLOBAL_FULL_PARACHAIN_HANDLER_COMPONENT.get() { + parachain_handler.extrinsics_factory.clone() + } else { + return Err(Error::NoParentchainAssigned) + }; + Ok(extrinsics_factory) +} + +pub(crate) fn get_stf_executor_from_solo_or_parachain() -> Result> { + let stf_executor = if let Ok(solochain_handler) = GLOBAL_FULL_SOLOCHAIN_HANDLER_COMPONENT.get() + { + solochain_handler.stf_executor.clone() + } else if let Ok(parachain_handler) = GLOBAL_FULL_PARACHAIN_HANDLER_COMPONENT.get() { + parachain_handler.stf_executor.clone() + } else { + return Err(Error::NoParentchainAssigned) + }; + Ok(stf_executor) +} diff --git a/tee-worker/enclave-runtime/x86_64-unknown-linux-sgx.json b/tee-worker/enclave-runtime/x86_64-unknown-linux-sgx.json new file mode 100644 index 0000000000..10d37a7490 --- /dev/null +++ b/tee-worker/enclave-runtime/x86_64-unknown-linux-sgx.json @@ -0,0 +1,31 @@ +{ + "arch": "x86_64", + "cpu": "x86-64", + "data-layout": "e-m:e-i64:64-f80:128-n8:16:32:64-S128", + "dynamic-linking": true, + "env": "sgx", + "exe-allocation-crate": "alloc_system", + "executables": true, + "has-elf-tls": true, + "has-rpath": true, + "linker-flavor": "gcc", + "linker-is-gnu": true, + "llvm-target": "x86_64-unknown-linux-gnu", + "max-atomic-width": 64, + "os": "linux", + "position-independent-executables": true, + "pre-link-args": { + "gcc": [ + "-Wl,--as-needed", + "-Wl,-z,noexecstack", + "-m64" + ] + }, + "relro-level": "full", + "stack-probes": true, + "target-c-int-width": "32", + "target-endian": "little", + "target-family": "unix", + "target-pointer-width": "64", + "vendor": "mesalock" +} diff --git a/tee-worker/extract_identity b/tee-worker/extract_identity new file mode 100755 index 0000000000..2c79268c15 --- /dev/null +++ b/tee-worker/extract_identity @@ -0,0 +1,28 @@ +#!/usr/bin/python3 + +import argparse + +if __name__ == "__main__": + parser = argparse.ArgumentParser() + parser.add_argument('--mrsigner', action="store_true") + args = parser.parse_args() + + line = "" + + searched_header = "enclave_hash.m" + output_header = "MRENCLAVE" + if args.mrsigner: + searched_header = "mrsigner->value" + output_header = "MRSIGNER" + while searched_header not in line: + line = input() + value = list() + line = input() + while line.startswith("0x"): + value += line.strip().split() + try: + line = input() + except: + break + value = "".join(map(lambda x: x.replace("0x",""), value)) +print("{}: {}".format(output_header, value)) diff --git a/tee-worker/lib/readme.txt b/tee-worker/lib/readme.txt new file mode 100644 index 0000000000..7951405f85 --- /dev/null +++ b/tee-worker/lib/readme.txt @@ -0,0 +1 @@ +lib \ No newline at end of file diff --git a/tee-worker/license_header_scs.txt b/tee-worker/license_header_scs.txt new file mode 100644 index 0000000000..6ded8ce2fd --- /dev/null +++ b/tee-worker/license_header_scs.txt @@ -0,0 +1,16 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ \ No newline at end of file diff --git a/tee-worker/litentry/core/assertion-build/Cargo.toml b/tee-worker/litentry/core/assertion-build/Cargo.toml new file mode 100644 index 0000000000..fee7aab270 --- /dev/null +++ b/tee-worker/litentry/core/assertion-build/Cargo.toml @@ -0,0 +1,99 @@ +[package] +authors = ["Litentry dev"] +edition = "2021" +name = "lc-assertion-build" +version = "0.1.0" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +# std dependencies +futures = { version = "0.3.8", optional = true } +hex = { version = "0.4.3", optional = true } +http = { version = "0.2", optional = true } +http_req = { optional = true, features = ["rust-tls"], branch = "master", git = "https://github.com/integritee-network/http_req" } +url = { version = "2.0.0", optional = true } + +# sgx dependencies +futures_sgx = { package = "futures", git = "https://github.com/mesalock-linux/futures-rs-sgx", optional = true } +hex-sgx = { package = "hex", git = "https://github.com/mesalock-linux/rust-hex-sgx", tag = "sgx_1.1.3", features = ["sgx_tstd"], optional = true } +http-sgx = { package = "http", git = "https://github.com/integritee-network/http-sgx.git", branch = "sgx-experimental", optional = true } +http_req-sgx = { optional = true, default-features = false, features = ["rust-tls", "sgx"], package = "http_req", git = "https://github.com/integritee-network/http_req" } +sgx_tstd = { branch = "master", git = "https://github.com/apache/teaclave-sgx-sdk.git", optional = true, features = ["net", "thread"] } +thiserror = { version = "1.0.26", optional = true } +thiserror_sgx = { package = "thiserror", git = "https://github.com/mesalock-linux/thiserror-sgx", tag = "sgx_1.1.3", optional = true } +url_sgx = { package = "url", git = "https://github.com/mesalock-linux/rust-url-sgx", tag = "sgx_1.1.3", optional = true } + +# no_std dependencies +codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } +lazy_static = { version = "1.1.0", features = ["spin_no_std"] } +log = { version = "0.4", default-features = false } +serde = { version = "1.0", default-features = false, features = ["derive", "alloc"] } +serde_json = { version = "1.0", default-features = false, features = ["alloc"] } + +# internal dependencies +ita-stf = { path = "../../../app-libs/stf", default-features = false } +itc-rest-client = { path = "../../../core/rest-client", default-features = false } +itp-ocall-api = { path = "../../../core-primitives/ocall-api", default-features = false } +itp-sgx-crypto = { path = "../../../core-primitives/sgx/crypto", default-features = false } +itp-sgx-externalities = { path = "../../../core-primitives/substrate-sgx/externalities", default-features = false } +itp-stf-executor = { path = "../../../core-primitives/stf-executor", default-features = false } +itp-storage = { path = "../../../core-primitives/storage", default-features = false } +itp-top-pool-author = { path = "../../../core-primitives/top-pool-author", default-features = false } +itp-types = { path = "../../../core-primitives/types", default-features = false } +itp-utils = { path = "../../../core-primitives/utils", default-features = false } + +frame-support = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.29", default-features = false } +sp-core = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.29", default-features = false } +sp-io = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.29", default-features = false } +sp-runtime = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.29", default-features = false } +sp-std = { git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.29", default-features = false } + +lc-data-providers = { path = "../data-providers", default-features = false } +lc-stf-task-sender = { path = "../stf-task/sender", default-features = false } +litentry-primitives = { path = "../../primitives", default-features = false } + +[features] +default = ["std"] +mockserver = ["lc-data-providers/mockserver"] +sgx = [ + "futures_sgx", + "hex-sgx", + "http-sgx", + "http_req-sgx", + "itc-rest-client/sgx", + "sgx_tstd", + "thiserror_sgx", + "url_sgx", + "ita-stf/sgx", + "itp-stf-executor/sgx", + "itp-sgx-externalities/sgx", + "itp-top-pool-author/sgx", + "litentry-primitives/sgx", + "lc-stf-task-sender/sgx", +] +std = [ + "futures", + "hex", + "http", + "http_req", + "itc-rest-client/std", + "log/std", + "serde/std", + "serde_json/std", + "thiserror", + "url", + "ita-stf/std", + "itp-types/std", + "itp-utils/std", + "itp-stf-executor/std", + "itp-top-pool-author/std", + "itp-sgx-externalities/std", + "itp-storage/std", + "sp-std/std", + "sp-io/std", + "sp-runtime/std", + "frame-support/std", + "litentry-primitives/std", + "lc-stf-task-sender/std", +] diff --git a/tee-worker/litentry/core/assertion-build/src/a1.rs b/tee-worker/litentry/core/assertion-build/src/a1.rs new file mode 100644 index 0000000000..05432ce791 --- /dev/null +++ b/tee-worker/litentry/core/assertion-build/src/a1.rs @@ -0,0 +1,51 @@ +// Copyright 2020-2022 Litentry Technologies GmbH. +// This file is part of Litentry. +// +// Litentry is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Litentry is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Litentry. If not, see . + +#[cfg(all(feature = "std", feature = "sgx"))] +compile_error!("feature \"std\" and feature \"sgx\" cannot be enabled at the same time"); + +#[cfg(all(not(feature = "std"), feature = "sgx"))] +extern crate sgx_tstd as std; + +// #[cfg(all(not(feature = "std"), feature = "sgx"))] +// use crate::sgx_reexport_prelude::*; + +use crate::{Error, Result}; +use lc_stf_task_sender::MaxIdentityLength; +use std::string::ToString; + +use litentry_primitives::Identity; +use sp_runtime::BoundedVec; + +pub fn build(identities: BoundedVec) -> Result<()> { + let mut web2_cnt = 0; + let mut web3_cnt = 0; + + for identity in &identities { + if identity.is_web2() { + web2_cnt += 1; + } else if identity.is_web3() { + web3_cnt += 1; + } + } + + if web2_cnt > 0 && web3_cnt > 0 { + // TODO: generate_vc(); + Ok(()) + } else { + Err(Error::Assertion1Error("Assertion1 fail.".to_string())) + } +} diff --git a/tee-worker/litentry/core/assertion-build/src/a2.rs b/tee-worker/litentry/core/assertion-build/src/a2.rs new file mode 100644 index 0000000000..bed06c27a0 --- /dev/null +++ b/tee-worker/litentry/core/assertion-build/src/a2.rs @@ -0,0 +1,67 @@ +// Copyright 2020-2022 Litentry Technologies GmbH. +// This file is part of Litentry. +// +// Litentry is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Litentry is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Litentry. If not, see . + +#[cfg(all(feature = "std", feature = "sgx"))] +compile_error!("feature \"std\" and feature \"sgx\" cannot be enabled at the same time"); + +#[cfg(all(not(feature = "std"), feature = "sgx"))] +extern crate sgx_tstd as std; + +use crate::{Error, Result}; +use std::format; + +use lc_data_providers::discord_litentry::DiscordLitentryClient; +use litentry_primitives::ParameterString; + +pub fn build(guild_id: ParameterString, handler: ParameterString) -> Result<()> { + let mut client = DiscordLitentryClient::new(); + match client.check_join(guild_id.into_inner(), handler.into_inner()) { + Err(e) => { + log::error!("error build assertion2: {:?}", e); + Err(Error::Assertion2Error(format!("{:?}", e))) + }, + Ok(_response) => { + // TODO: + // generate_vc(who, identity, ...) + + // After receiving VC, F/E is expected to assign 'IDHubber' role and align with bot + // https://github.com/litentry/tee-worker/issues/35 + // https://github.com/litentry/tee-worker/issues/36 + + Ok(()) + }, + } +} + +#[cfg(test)] +mod tests { + use crate::a2::build; + use frame_support::BoundedVec; + use log; + + #[test] + fn assertion2_verification_works() { + let guildid: u64 = 919848390156767232; + let guild_id_vec: Vec = format!("{}", guildid).as_bytes().to_vec(); + let handler_vec: Vec = "againstwar%234779".to_string().as_bytes().to_vec(); + + let guild_id = BoundedVec::try_from(guild_id_vec).unwrap(); + let handler = BoundedVec::try_from(handler_vec).unwrap(); + + let _ = build(guild_id, handler); + log::info!("assertion2 test"); + } +} diff --git a/tee-worker/litentry/core/assertion-build/src/a3.rs b/tee-worker/litentry/core/assertion-build/src/a3.rs new file mode 100644 index 0000000000..4d12a2016e --- /dev/null +++ b/tee-worker/litentry/core/assertion-build/src/a3.rs @@ -0,0 +1,63 @@ +// Copyright 2020-2022 Litentry Technologies GmbH. +// This file is part of Litentry. +// +// Litentry is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Litentry is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Litentry. If not, see . + +#[cfg(all(feature = "std", feature = "sgx"))] +compile_error!("feature \"std\" and feature \"sgx\" cannot be enabled at the same time"); + +#[cfg(all(not(feature = "std"), feature = "sgx"))] +extern crate sgx_tstd as std; + +use crate::{Error, Result}; +use std::format; + +use lc_data_providers::discord_litentry::DiscordLitentryClient; +use litentry_primitives::ParameterString; + +pub fn build(guild_id: ParameterString, handler: ParameterString) -> Result<()> { + let mut client = DiscordLitentryClient::new(); + match client.check_id_hubber(guild_id.into_inner(), handler.into_inner()) { + Err(e) => { + log::error!("error build assertion2: {:?}", e); + Err(Error::Assertion3Error(format!("{:?}", e))) + }, + Ok(_response) => { + // TODO: + // generate_vc(who, identity, ...) + + Ok(()) + }, + } +} + +#[cfg(test)] +mod tests { + use crate::a3::build; + use frame_support::BoundedVec; + use log; + + #[test] + fn assertion3_verification_works() { + let guildid: u64 = 919848390156767232; + let guild_id_vec: Vec = format!("{}", guildid).as_bytes().to_vec(); + let handler_vec: Vec = "ericzhang.eth#0114".to_string().as_bytes().to_vec(); + + let guild_id = BoundedVec::try_from(guild_id_vec).unwrap(); + let handler = BoundedVec::try_from(handler_vec).unwrap(); + + let _ = build(guild_id, handler); + log::info!("assertion3 test"); + } +} diff --git a/tee-worker/litentry/core/assertion-build/src/a5.rs b/tee-worker/litentry/core/assertion-build/src/a5.rs new file mode 100644 index 0000000000..75cafc95d7 --- /dev/null +++ b/tee-worker/litentry/core/assertion-build/src/a5.rs @@ -0,0 +1,77 @@ +// Copyright 2020-2022 Litentry Technologies GmbH. +// This file is part of Litentry. +// +// Litentry is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Litentry is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Litentry. If not, see . + +#[cfg(all(feature = "std", feature = "sgx"))] +compile_error!("feature \"std\" and feature \"sgx\" cannot be enabled at the same time"); + +#[cfg(all(not(feature = "std"), feature = "sgx"))] +extern crate sgx_tstd as std; + +use crate::Error; +use lc_data_providers::{ + twitter_litentry::TwitterLitentryClient, twitter_official::TwitterOfficialClient, +}; +use litentry_primitives::{ + Identity, IdentityHandle, IdentityWebType, ParameterString, Web2Network, +}; +use std::{ + string::{String, ToString}, + vec::Vec, +}; + +pub fn build( + identities: Vec, + twitter_account: ParameterString, + original_tweet_id: ParameterString, +) -> Result<(), Error> { + let mut twitter_litentry_client = TwitterLitentryClient::new(); + let mut twitter_official_client = TwitterOfficialClient::new(); + for identity in identities { + if identity.web_type == IdentityWebType::Web2(Web2Network::Twitter) { + if let IdentityHandle::String(twitter_id) = identity.handle { + let twitter_id = twitter_id.to_vec(); + match twitter_litentry_client + .check_follow(twitter_id.clone(), twitter_account.to_vec()) + { + Ok(true) => { + match twitter_official_client + .query_retweet(twitter_id, original_tweet_id.to_vec()) + { + Ok(_) => { + // TODO generate vc; + return Ok(()) + }, + Err(e) => { + log::warn!("Assertion5 query_retweet error:{:?}", e) + }, + } + }, + Ok(false) => { + log::debug!( + "account:{:?} don't follow {:?}", + twitter_id, + String::from_utf8(twitter_account.to_vec()) + ); + }, + Err(e) => { + log::warn!("Assertion5 request error:{:?}", e) + }, + } + } + } + } + Err(Error::Assertion5Error("not match any identities".to_string())) +} diff --git a/tee-worker/litentry/core/assertion-build/src/a6.rs b/tee-worker/litentry/core/assertion-build/src/a6.rs new file mode 100644 index 0000000000..4cc303db39 --- /dev/null +++ b/tee-worker/litentry/core/assertion-build/src/a6.rs @@ -0,0 +1,73 @@ +// Copyright 2020-2022 Litentry Technologies GmbH. +// This file is part of Litentry. +// +// Litentry is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Litentry is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Litentry. If not, see . + +#[cfg(all(feature = "std", feature = "sgx"))] +compile_error!("feature \"std\" and feature \"sgx\" cannot be enabled at the same time"); + +#[cfg(all(not(feature = "std"), feature = "sgx"))] +extern crate sgx_tstd as std; + +use crate::Error; +use lc_data_providers::twitter_official::TwitterOfficialClient; +use litentry_primitives::{Identity, IdentityHandle, IdentityWebType, Web2Network}; +use std::vec::Vec; + +/// Following ranges: +/// +/// * 1+ follower +/// * 100+ followers +/// * 1,000+ followers +/// * 10,000+ followers +/// * 100,000+ followers +pub fn build(identities: Vec) -> Result<(), Error> { + let mut client = TwitterOfficialClient::new(); + let mut sum: u32 = 0; + for identity in identities { + if identity.web_type == IdentityWebType::Web2(Web2Network::Twitter) { + if let IdentityHandle::String(twitter_id) = identity.handle { + match client.query_user(twitter_id.to_vec()) { + Ok(user) => { + sum += user.public_metrics.followers_count; + }, + Err(e) => { + log::warn!("Assertion6 request error:{:?}", e) + }, + } + } + } + } + match sum { + 0 | 1 => { + log::warn!("level 0"); + }, + 2..=100 => { + log::warn!("level 1"); + }, + 101..=1000 => { + log::warn!("level 2"); + }, + 1001..=10000 => { + log::warn!("level 3"); + }, + 10001..=100000 => { + log::warn!("level 4"); + }, + 100001..=u32::MAX => { + log::warn!("level 5"); + }, + } + Ok(()) +} diff --git a/tee-worker/litentry/core/assertion-build/src/lib.rs b/tee-worker/litentry/core/assertion-build/src/lib.rs new file mode 100644 index 0000000000..6c9c036d83 --- /dev/null +++ b/tee-worker/litentry/core/assertion-build/src/lib.rs @@ -0,0 +1,61 @@ +// Copyright 2020-2022 Litentry Technologies GmbH. +// This file is part of Litentry. +// +// Litentry is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Litentry is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Litentry. If not, see . + +#![cfg_attr(not(feature = "std"), no_std)] + +#[cfg(all(feature = "std", feature = "sgx"))] +compile_error!("feature \"std\" and feature \"sgx\" cannot be enabled at the same time"); + +#[cfg(all(not(feature = "std"), feature = "sgx"))] +extern crate sgx_tstd as std; + +// re-export module to properly feature gate sgx and regular std environment +#[cfg(all(not(feature = "std"), feature = "sgx"))] +pub mod sgx_reexport_prelude { + pub use thiserror_sgx as thiserror; + pub use url_sgx as url; +} + +#[cfg(all(not(feature = "std"), feature = "sgx"))] +use crate::sgx_reexport_prelude::*; + +use std::{fmt::Debug, string::String}; + +pub mod a1; +pub mod a2; +pub mod a3; +pub mod a5; +pub mod a6; + +pub type Result = core::result::Result; + +#[derive(Debug, thiserror::Error, Clone)] +pub enum Error { + #[error("Assertion1 error: {0}")] + Assertion1Error(String), + + #[error("Assertion2 error: {0}")] + Assertion2Error(String), + + #[error("Assertion3 error: {0}")] + Assertion3Error(String), + + #[error("Assertion5 error: {0}")] + Assertion5Error(String), + + #[error("Other error: {0}")] + AssertionOtherError(String), +} diff --git a/tee-worker/litentry/core/data-providers/Cargo.toml b/tee-worker/litentry/core/data-providers/Cargo.toml new file mode 100644 index 0000000000..8a23dd54fb --- /dev/null +++ b/tee-worker/litentry/core/data-providers/Cargo.toml @@ -0,0 +1,65 @@ +[package] +authors = ["Litentry Dev"] +edition = "2021" +name = "lc-data-providers" +version = "0.1.0" + + +[dependencies] +# std dependencies +hex = { version = "0.4.3", optional = true } +http = { version = "0.2", optional = true } +http_req = { optional = true, features = ["rust-tls"], branch = "master", git = "https://github.com/integritee-network/http_req" } +thiserror = { version = "1.0.26", optional = true } +url = { version = "2.0.0", optional = true } + +# no_std dependencies +log = { version = "0.4", default-features = false } +serde = { version = "1.0", default-features = false, features = ["derive", "alloc"] } +serde_json = { version = "1.0", default-features = false, features = ["alloc"] } + +# internal dependencies +itc-rest-client = { path = "../../../core/rest-client", default-features = false } + +# sgx dependencies +hex-sgx = { package = "hex", git = "https://github.com/mesalock-linux/rust-hex-sgx", tag = "sgx_1.1.3", features = ["sgx_tstd"], optional = true } +http-sgx = { package = "http", git = "https://github.com/integritee-network/http-sgx.git", branch = "sgx-experimental", optional = true } +http_req-sgx = { package = "http_req", git = "https://github.com/integritee-network/http_req", default-features = false, features = ["rust-tls", "sgx"], optional = true } +sgx_tstd = { git = "https://github.com/apache/teaclave-sgx-sdk.git", branch = "master", features = ["net", "thread"], optional = true } +thiserror_sgx = { package = "thiserror", git = "https://github.com/mesalock-linux/thiserror-sgx", tag = "sgx_1.1.3", optional = true } +url_sgx = { package = "url", git = "https://github.com/mesalock-linux/rust-url-sgx", tag = "sgx_1.1.3", optional = true } + +# litentry +litentry-primitives = { path = "../../primitives", default-features = false } + +[dev-dependencies] +httpmock = "0.6" +lc-mock-server = { path = "../mock-server" } +sp-core = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.29", default-features = false } + + +[features] +default = ["std"] +mockserver = [] +sgx = [ + "hex-sgx", + "http-sgx", + "http_req-sgx", + "thiserror_sgx", + "url_sgx", + "sgx_tstd", + "itc-rest-client/sgx", + "litentry-primitives/sgx", +] +std = [ + "hex", + "http", + "http_req", + "thiserror", + "url", + "itc-rest-client/std", + "log/std", + "serde/std", + "serde_json/std", + "litentry-primitives/std", +] diff --git a/tee-worker/litentry/core/data-providers/src/discord_litentry.rs b/tee-worker/litentry/core/data-providers/src/discord_litentry.rs new file mode 100644 index 0000000000..5942ae7ee7 --- /dev/null +++ b/tee-worker/litentry/core/data-providers/src/discord_litentry.rs @@ -0,0 +1,178 @@ +// Copyright 2020-2022 Litentry Technologies GmbH. +// This file is part of Litentry. +// +// Litentry is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Litentry is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Litentry. If not, see . + +#[cfg(all(not(feature = "std"), feature = "sgx"))] +use crate::sgx_reexport_prelude::*; + +use crate::{base_url::DISCORD_LITENTRY, build_client, vec_to_string, Error, HttpError}; +use http::header::CONNECTION; +use http_req::response::Headers; + +use itc_rest_client::{ + http_client::{DefaultSend, HttpClient}, + rest_client::RestClient, + RestGet, RestPath, +}; +use serde::{Deserialize, Serialize}; +use std::{ + default::Default, + format, + string::{String, ToString}, + vec, + vec::Vec, +}; + +#[derive(Serialize, Deserialize, Debug)] +#[serde(rename_all = "camelCase")] +pub struct DiscordResponse { + pub data: bool, + pub message: String, + pub has_errors: bool, + pub msg_code: u32, + pub success: bool, +} + +impl RestPath for DiscordResponse { + fn get_path(path: String) -> core::result::Result { + Ok(path) + } +} + +pub struct DiscordLitentryClient { + client: RestClient>, +} + +impl Default for DiscordLitentryClient { + fn default() -> Self { + Self::new() + } +} + +impl DiscordLitentryClient { + pub fn new() -> Self { + let mut headers = Headers::new(); + headers.insert(CONNECTION.as_str(), "close"); + + let client = build_client(DISCORD_LITENTRY, headers); + DiscordLitentryClient { client } + } + + pub fn check_join( + &mut self, + guild_id: Vec, + handler: Vec, + ) -> Result { + let guild_id_s = vec_to_string(guild_id)?; + let handler_s = vec_to_string(handler)?; + + let path = "/discord/joined".to_string(); + let query = vec![("guildid", guild_id_s.as_str()), ("handler", handler_s.as_str())]; + self.client + .get_with::(path, query.as_slice()) + .map_err(|e| Error::RequestError(format!("{:?}", e))) + } + + pub fn check_id_hubber( + &mut self, + guild_id: Vec, + handler: Vec, + ) -> Result { + let guild_id_s = vec_to_string(guild_id)?; + let handler_s = vec_to_string(handler)?; + let path = "/discord/commented/idhubber".to_string(); + let query = vec![("guildid", guild_id_s.as_str()), ("handler", handler_s.as_str())]; + + let res = self + .client + .get_with::(path, query.as_slice()) + .map_err(|e| Error::RequestError(format!("{:?}", e))); + + res + } +} + +#[cfg(test)] +mod tests { + use super::*; + use httpmock::prelude::*; + use lc_mock_server::standalone_server; + use std::vec::Vec; + + #[test] + fn check_join_work() { + standalone_server(); + let server = httpmock::MockServer::connect("localhost:9527"); + + let body = DiscordResponse { + data: true, + message: "success".into(), + has_errors: false, + msg_code: 200, + success: true, + }; + + let path = "/discord/joined"; + server.mock(|when, then| { + when.method(GET) + .path(path) + .query_param("guildid", "919848390156767232") + .query_param("handler", "againstwar#4779"); + then.status(200).body(serde_json::to_string(&body).unwrap()); + }); + + let guildid = "919848390156767232"; + let handler = "againstwar#4779"; + let guild_id_vec = guildid.as_bytes().to_vec(); + let handler_vec = handler.as_bytes().to_vec(); + + let mut client = DiscordLitentryClient::new(); + let response = client.check_join(guild_id_vec, handler_vec); + + assert!(response.is_ok(), "check join discord error: {:?}", response); + } + + #[test] + fn check_id_hubber_work() { + standalone_server(); + let server = httpmock::MockServer::connect("localhost:9527"); + + let body = DiscordResponse { + data: true, + message: "success".into(), + has_errors: false, + msg_code: 200, + success: true, + }; + + server.mock(|when, then| { + when.method(GET) + .path("/discord/commented/idhubber") + .query_param("guildid", "919848390156767232") + .query_param("handler", "ericzhang.eth#0114"); + + then.status(200).body(serde_json::to_string(&body).unwrap()); + }); + + let guildid: u64 = 919848390156767232; + let guild_id_vec: Vec = format!("{}", guildid).as_bytes().to_vec(); + let handler_vec: Vec = "ericzhang.eth#0114".as_bytes().to_vec(); + + let mut client = DiscordLitentryClient::new(); + let response = client.check_id_hubber(guild_id_vec, handler_vec); + + assert!(response.is_ok(), "check discord id hubber error: {:?}", response); + } +} diff --git a/tee-worker/litentry/core/data-providers/src/discord_official.rs b/tee-worker/litentry/core/data-providers/src/discord_official.rs new file mode 100644 index 0000000000..9de7b9b0c4 --- /dev/null +++ b/tee-worker/litentry/core/data-providers/src/discord_official.rs @@ -0,0 +1,131 @@ +// Copyright 2020-2022 Litentry Technologies GmbH. +// This file is part of Litentry. +// +// Litentry is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Litentry is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Litentry. If not, see . + +#[cfg(all(not(feature = "std"), feature = "sgx"))] +use crate::sgx_reexport_prelude::*; + +use crate::{base_url::DISCORD_OFFICIAL, build_client, vec_to_string, Error, HttpError, UserInfo}; +use http::header::{AUTHORIZATION, CONNECTION}; +use http_req::response::Headers; +use itc_rest_client::{ + http_client::{DefaultSend, HttpClient}, + rest_client::RestClient, + RestGet, RestPath, +}; +use serde::{Deserialize, Serialize}; +use std::{default::Default, format, string::String, vec, vec::Vec}; + +#[derive(Serialize, Deserialize, Debug)] +pub struct DiscordMessage { + pub id: String, // message_id + pub channel_id: String, + pub content: String, + pub author: DiscordMessageAuthor, +} + +#[derive(Serialize, Deserialize, Debug)] +pub struct DiscordMessageAuthor { + pub id: String, //user_id + pub username: String, +} + +impl Default for DiscordOfficialClient { + fn default() -> Self { + Self::new() + } +} + +impl RestPath for DiscordMessage { + fn get_path(path: String) -> Result { + Ok(path) + } +} + +impl UserInfo for DiscordMessage { + fn get_user_id(&self) -> Option { + Some(self.author.id.clone()) + } +} + +pub struct DiscordOfficialClient { + client: RestClient>, +} + +impl DiscordOfficialClient { + pub fn new() -> Self { + let mut headers = Headers::new(); + headers.insert(CONNECTION.as_str(), "close"); + let token = std::env::var("DISCORD_AUTHORIZATION_TOKEN"); + if let Ok(token) = token { + headers.insert(AUTHORIZATION.as_str(), token.as_str()); + } + let client = build_client(DISCORD_OFFICIAL, headers); + DiscordOfficialClient { client } + } + + pub fn query_message( + &mut self, + channel_id: Vec, + message_id: Vec, + ) -> Result { + let channel_id = vec_to_string(channel_id)?; + let message_id = vec_to_string(message_id)?; + let path = format!("/api/channels/{}/messages/{}", channel_id, message_id); + let query = vec![]; + self.client + .get_with::(path, query.as_slice()) + .map_err(|e| Error::RequestError(format!("{:?}", e))) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use httpmock::prelude::*; + use lc_mock_server::standalone_server; + + #[test] + fn query_message_work() { + standalone_server(); + let server = httpmock::MockServer::connect("localhost:9527"); + + let channel_id = "919848392035794945"; + let message_id = "1"; + + let user_id = "001"; + let username = "elon"; + let author = DiscordMessageAuthor { id: user_id.into(), username: username.into() }; + + let body = DiscordMessage { + id: message_id.into(), + channel_id: channel_id.into(), + content: "Hello, elon.".into(), + author, + }; + + let path = format! {"/api/channels/{}/messages/{}", channel_id, message_id}; + server.mock(|when, then| { + when.method(GET).path(path); + then.status(200).body(serde_json::to_string(&body).unwrap()); + }); + + let mut client = DiscordOfficialClient::new(); + let channel_id = "919848392035794945".as_bytes().to_vec(); + let message_id = "1".as_bytes().to_vec(); + let result = client.query_message(channel_id, message_id); + assert!(result.is_ok(), "query discord error: {:?}", result); + } +} diff --git a/tee-worker/litentry/core/data-providers/src/lib.rs b/tee-worker/litentry/core/data-providers/src/lib.rs new file mode 100644 index 0000000000..fdc487d730 --- /dev/null +++ b/tee-worker/litentry/core/data-providers/src/lib.rs @@ -0,0 +1,103 @@ +// Copyright 2020-2022 Litentry Technologies GmbH. +// This file is part of Litentry. +// +// Litentry is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Litentry is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Litentry. If not, see . + +#![cfg_attr(not(feature = "std"), no_std)] + +#[cfg(all(not(feature = "std"), feature = "sgx"))] +extern crate sgx_tstd as std; + +// re-export module to properly feature gate sgx and regular std environment +#[cfg(all(not(feature = "std"), feature = "sgx"))] +pub mod sgx_reexport_prelude { + pub use hex_sgx as hex; + pub use http_req_sgx as http_req; + pub use http_sgx as http; + pub use thiserror_sgx as thiserror; + pub use url_sgx as url; +} + +#[cfg(all(not(feature = "std"), feature = "sgx"))] +use crate::sgx_reexport_prelude::*; + +use core::time::Duration; +use http_req::response::Headers; +use itc_rest_client::{ + error::Error as HttpError, + http_client::{DefaultSend, HttpClient}, + rest_client::RestClient, +}; +use std::{ + string::{String, ToString}, + vec::Vec, +}; +use url::Url; + +#[cfg(all(feature = "std", feature = "sgx"))] +compile_error!("feature \"std\" and feature \"sgx\" cannot be enabled at the same time"); + +pub mod discord_litentry; +pub mod discord_official; +pub mod twitter_litentry; +pub mod twitter_official; + +const TIMEOUT: Duration = Duration::from_secs(3u64); + +#[cfg(all(not(test), not(feature = "mockserver")))] +pub mod base_url { + pub(crate) const TWITTER_OFFICIAL: &str = "https://api.twitter.com"; + pub(crate) const TWITTER_LITENTRY: &str = "http://47.57.13.126:8080"; + + pub(crate) const DISCORD_OFFICIAL: &str = "https://discordapp.com"; + pub(crate) const DISCORD_LITENTRY: &str = "http://47.57.13.126:8080"; +} + +// #[cfg(test)] +#[cfg(any(test, feature = "mockserver"))] +pub mod base_url { + pub(crate) const TWITTER_OFFICIAL: &str = "http://localhost:9527"; + pub(crate) const TWITTER_LITENTRY: &str = "http://localhost:9527"; + pub(crate) const DISCORD_OFFICIAL: &str = "http://localhost:9527"; + pub(crate) const DISCORD_LITENTRY: &str = "http://localhost:9527"; +} + +#[derive(Debug, thiserror::Error, Clone)] +pub enum Error { + #[error("Request error: {0}")] + RequestError(String), + + #[error("UTF8 error: {0}")] + Utf8Error(String), +} + +pub trait UserInfo { + fn get_user_id(&self) -> Option; +} + +pub fn vec_to_string(vec: Vec) -> Result { + let tmp = String::from_utf8(vec.to_vec()).map_err(|e| Error::Utf8Error(e.to_string()))?; + let tmp = tmp.trim(); + if tmp.is_empty() { + return Err(Error::Utf8Error("empty string".to_string())) + } + Ok(tmp.to_string()) +} + +pub fn build_client(base_url: &str, headers: Headers) -> RestClient> { + // println!("base_url: {}", base_url); + let base_url = Url::parse(base_url).unwrap(); + let http_client = HttpClient::new(DefaultSend {}, true, Some(TIMEOUT), Some(headers), None); + RestClient::new(http_client, base_url) +} diff --git a/tee-worker/litentry/core/data-providers/src/twitter_litentry.rs b/tee-worker/litentry/core/data-providers/src/twitter_litentry.rs new file mode 100644 index 0000000000..d3bc3a0b98 --- /dev/null +++ b/tee-worker/litentry/core/data-providers/src/twitter_litentry.rs @@ -0,0 +1,112 @@ +// Copyright 2020-2022 Litentry Technologies GmbH. +// This file is part of Litentry. +// +// Litentry is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Litentry is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Litentry. If not, see . + +#[cfg(all(not(feature = "std"), feature = "sgx"))] +use crate::sgx_reexport_prelude::*; + +use crate::{base_url::TWITTER_LITENTRY, build_client, vec_to_string, Error, HttpError}; +use http::header::CONNECTION; +use http_req::response::Headers; +use itc_rest_client::{ + http_client::{DefaultSend, HttpClient}, + rest_client::RestClient, + RestGet, RestPath, +}; +use serde::{Deserialize, Serialize}; +use std::{ + default::Default, + format, + string::{String, ToString}, + vec, + vec::Vec, +}; + +pub struct TwitterLitentryClient { + client: RestClient>, +} + +impl Default for TwitterLitentryClient { + fn default() -> Self { + Self::new() + } +} + +#[derive(Serialize, Deserialize, Debug)] +pub struct CheckFollow { + #[serde(rename(deserialize = "data"))] + result: bool, +} + +impl RestPath for CheckFollow { + fn get_path(path: String) -> core::result::Result { + Ok(path) + } +} + +impl TwitterLitentryClient { + pub fn new() -> Self { + let mut headers = Headers::new(); + headers.insert(CONNECTION.as_str(), "close"); + let client = build_client(TWITTER_LITENTRY, headers); + TwitterLitentryClient { client } + } + + /// check if the source account follow the target account. + pub fn check_follow(&mut self, source: Vec, target: Vec) -> Result { + let source = vec_to_string(source)?; + let target = vec_to_string(target)?; + let query = vec![("handler1", target.as_str()), ("handler2", source.as_str())]; + let response = self + .client + .get_with::( + "twitter/followers/verification".to_string(), + query.as_slice(), + ) + .map_err(|e| Error::RequestError(format!("{:?}", e)))?; + + Ok(response.result) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use httpmock::prelude::*; + use lc_mock_server::standalone_server; + + #[test] + fn check_follow_work() { + standalone_server(); + let server = httpmock::MockServer::connect("localhost:9527"); + + let body = r#"{ "data": false }"#; + let path = "/twitter/followers/verification"; + server.mock(|when, then| { + when.method(GET) + .path(path) + .query_param("handler1", "litentry") + .query_param("handler2", "ericzhangeth"); + then.status(200).body(body); + }); + + let mut client = TwitterLitentryClient::new(); + let source = "ericzhangeth".as_bytes().to_vec(); + let target = "litentry".as_bytes().to_vec(); + + let result = client.check_follow(source, target); + assert!(result.is_ok(), "error: {:?}", result); + } +} diff --git a/tee-worker/litentry/core/data-providers/src/twitter_official.rs b/tee-worker/litentry/core/data-providers/src/twitter_official.rs new file mode 100644 index 0000000000..c8eb3417e0 --- /dev/null +++ b/tee-worker/litentry/core/data-providers/src/twitter_official.rs @@ -0,0 +1,295 @@ +// Copyright 2020-2022 Litentry Technologies GmbH. +// This file is part of Litentry. +// +// Litentry is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Litentry is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Litentry. If not, see . + +#[cfg(all(not(feature = "std"), feature = "sgx"))] +use crate::sgx_reexport_prelude::*; + +use crate::{base_url::TWITTER_OFFICIAL, build_client, vec_to_string, Error, HttpError, UserInfo}; +use http::header::{AUTHORIZATION, CONNECTION}; +use http_req::response::Headers; +use serde::{Deserialize, Serialize}; +use std::{ + default::Default, + format, + string::{String, ToString}, + vec, + vec::Vec, +}; + +use itc_rest_client::{ + http_client::{DefaultSend, HttpClient}, + rest_client::RestClient, + RestGet, RestPath, +}; + +#[derive(Serialize, Deserialize, Debug)] +pub struct TwitterAPIV2Response { + pub data: Option, + pub meta: Option, +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct ResponseMeta { + pub result_count: u32, +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct Tweet { + pub author_id: String, + pub id: String, + pub text: String, +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct TwitterUser { + pub id: String, + pub name: String, + pub username: String, + pub public_metrics: TwitterUserPublicMetrics, +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct TwitterUserPublicMetrics { + pub followers_count: u32, + pub following_count: u32, +} + +impl RestPath for Tweet { + fn get_path(path: String) -> core::result::Result { + Ok(path) + } +} + +impl RestPath for TwitterAPIV2Response { + fn get_path(path: String) -> core::result::Result { + Ok(path) + } +} + +impl UserInfo for Tweet { + fn get_user_id(&self) -> Option { + Some(self.author_id.clone()) + } +} + +pub struct TwitterOfficialClient { + client: RestClient>, +} + +impl Default for TwitterOfficialClient { + fn default() -> Self { + Self::new() + } +} + +/// rate limit: https://developer.twitter.com/en/docs/twitter-api/rate-limits +impl TwitterOfficialClient { + pub fn new() -> Self { + let mut headers = Headers::new(); + headers.insert(CONNECTION.as_str(), "close"); + let token = std::env::var("TWITTER_AUTHORIZATION_TOKEN"); + if let Ok(token) = token { + headers.insert(AUTHORIZATION.as_str(), token.as_str()); + } + let client = build_client(TWITTER_OFFICIAL, headers); + TwitterOfficialClient { client } + } + + /// rate limit: 300/15min(per App) 900/15min(per User) + pub fn query_tweet(&mut self, tweet_id: Vec) -> Result { + let tweet_id = vec_to_string(tweet_id)?; + let path = format!("/2/tweets/{}", tweet_id); + let query: Vec<(&str, &str)> = + vec![("ids", tweet_id.as_str()), ("expansions", "author_id")]; + self.client + .get_with::(path, query.as_slice()) + .map_err(|e| Error::RequestError(format!("{:?}", e))) + } + + /// rate limit: 450/15min(per App) 180/15min(per User) + /// + /// Building queries for Search Tweets: https://developer.twitter.com/en/docs/twitter-api/tweets/search/integrate/build-a-query + pub fn query_retweet( + &mut self, + user: Vec, + original_tweet_id: Vec, + ) -> Result { + let original_tweet_id = vec_to_string(original_tweet_id)?; + let user = vec_to_string(user)?; + let query_value = format!("from: {} retweets_of_tweet_id: {}", user, original_tweet_id); + let query: Vec<(&str, &str)> = + vec![("query", query_value.as_str()), ("expansions", "author_id")]; + let resp = self + .client + .get_with::>>( + "/2/tweets/search/recent".to_string(), + query.as_slice(), + ) + .map_err(|e| Error::RequestError(format!("{:?}", e)))?; + let tweets = resp.data.ok_or_else(|| Error::RequestError("tweet not found".to_string()))?; + if !tweets.is_empty() { + Ok(tweets.get(0).unwrap().clone()) + } else { + Err(Error::RequestError("tweet not found".to_string())) + } + } + + /// rate limit: 300/15min(per App) 900/15min(per User) + pub fn query_user(&mut self, user: Vec) -> Result { + let user = vec_to_string(user)?; + let query = vec![("user.fields", "public_metrics")]; + let resp = self + .client + .get_with::>( + format!("/2/users/{}", user), + query.as_slice(), + ) + .map_err(|e| Error::RequestError(format!("{:?}", e)))?; + let user = resp.data.ok_or_else(|| Error::RequestError("user not found".to_string()))?; + Ok(user) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use httpmock::prelude::*; + use lc_mock_server::{mock_tweet_payload, standalone_server}; + use litentry_primitives::{ + ChallengeCode, Identity, IdentityHandle, IdentityString, IdentityWebType, Web2Network, + }; + use sp_core::crypto::AccountId32 as AccountId; + + #[test] + fn query_tweet_work() { + standalone_server(); + let server = httpmock::MockServer::connect("localhost:9527"); + + let tweet_id = "100"; + + let account_id = AccountId::new([0u8; 32]); + let twitter_identity = Identity { + web_type: IdentityWebType::Web2(Web2Network::Twitter), + handle: IdentityHandle::String( + IdentityString::try_from("litentry".as_bytes().to_vec()).unwrap(), + ), + }; + let chanllenge_code: ChallengeCode = + [8, 104, 90, 56, 35, 213, 18, 250, 213, 210, 119, 241, 2, 174, 24, 8]; + let payload = mock_tweet_payload(&account_id, &twitter_identity, &chanllenge_code); + + let tweet = Tweet { + author_id: "ericzhangeth".into(), + id: tweet_id.into(), + text: serde_json::to_string(&payload).unwrap(), + }; + + let path = format! {"/2/tweets/{}", tweet_id}; + server.mock(|when, then| { + when.method(GET) + .path(path) + .query_param("ids", tweet_id) + .query_param("expansions", "author_id"); + then.status(200).body(serde_json::to_string(&tweet).unwrap()); + }); + + let mut client = TwitterOfficialClient::new(); + let result = client.query_tweet(tweet_id.as_bytes().to_vec()); + assert!(result.is_ok(), "error: {:?}", result); + } + + #[test] + fn query_retweet_work() { + standalone_server(); + let server = httpmock::MockServer::connect("localhost:9527"); + + let author_id = "ericzhangeth"; + let id = "100"; + + let account_id = AccountId::new([0u8; 32]); + let twitter_identity = Identity { + web_type: IdentityWebType::Web2(Web2Network::Twitter), + handle: IdentityHandle::String( + IdentityString::try_from("litentry".as_bytes().to_vec()).unwrap(), + ), + }; + let chanllenge_code: ChallengeCode = + [8, 104, 90, 56, 35, 213, 18, 250, 213, 210, 119, 241, 2, 174, 24, 8]; + let payload = mock_tweet_payload(&account_id, &twitter_identity, &chanllenge_code); + + let tweets = vec![Tweet { + author_id: author_id.into(), + id: id.into(), + text: serde_json::to_string(&payload).unwrap(), + }]; + let body = TwitterAPIV2Response { data: Some(tweets), meta: None }; + + let path = "/2/tweets/search/recent"; + + let user = "ericzhangeth"; + let original_tweet_id = "100"; + let query_value = format!("from: {} retweets_of_tweet_id: {}", user, original_tweet_id); + + server.mock(|when, then| { + when.method(GET) + .path(path) + .query_param("query", query_value) + .query_param("expansions", "author_id"); + then.status(200).body(serde_json::to_string(&body).unwrap()); + }); + + let mut client = TwitterOfficialClient::new(); + + let user = author_id.clone().as_bytes().to_vec(); + let original_tweet_id = id.as_bytes().to_vec(); + let response = client.query_retweet(user, original_tweet_id); + + assert!(response.is_ok(), "error: {:?}", response); + } + + #[test] + fn query_user_work() { + std::env::set_var("TWITTER_AUTHORIZATION_TOKEN", "Bearer "); + + standalone_server(); + let server = httpmock::MockServer::connect("localhost:9527"); + + let user = "1256908613857226756"; + + let twitter_user_data = TwitterUser { + id: user.into(), + name: "ericzhang".into(), + username: "elon".into(), + public_metrics: TwitterUserPublicMetrics { + followers_count: 100_u32, + following_count: 99_u32, + }, + }; + + let body = TwitterAPIV2Response { data: Some(twitter_user_data), meta: None }; + + let path = format! {"/2/users/{}", user}; + + server.mock(|when, then| { + when.method(GET).path(path).query_param("user.fields", "public_metrics"); + then.status(200).body(serde_json::to_string(&body).unwrap()); + }); + + let mut client = TwitterOfficialClient::new(); + let result = client.query_user(user.as_bytes().to_vec()); + assert!(result.is_ok(), "error: {:?}", result); + } +} diff --git a/tee-worker/litentry/core/identity-verification/Cargo.toml b/tee-worker/litentry/core/identity-verification/Cargo.toml new file mode 100644 index 0000000000..04c2dbf54a --- /dev/null +++ b/tee-worker/litentry/core/identity-verification/Cargo.toml @@ -0,0 +1,92 @@ +[package] +authors = ["Litentry dev"] +edition = "2021" +name = "lc-identity-verification" +version = "0.1.0" + +[dependencies] +# std dependencies +futures = { version = "0.3.8", optional = true } +hex = { version = "0.4.3", optional = true } +http = { version = "0.2", optional = true } +http_req = { optional = true, features = ["rust-tls"], branch = "master", git = "https://github.com/integritee-network/http_req" } +url = { version = "2.0.0", optional = true } + +# sgx dependencies +futures_sgx = { package = "futures", git = "https://github.com/mesalock-linux/futures-rs-sgx", optional = true } +hex-sgx = { package = "hex", git = "https://github.com/mesalock-linux/rust-hex-sgx", tag = "sgx_1.1.3", features = ["sgx_tstd"], optional = true } +http-sgx = { package = "http", git = "https://github.com/integritee-network/http-sgx.git", branch = "sgx-experimental", optional = true } +http_req-sgx = { package = "http_req", git = "https://github.com/integritee-network/http_req", default-features = false, features = ["rust-tls", "sgx"], optional = true } +sgx_tstd = { git = "https://github.com/apache/teaclave-sgx-sdk.git", branch = "master", features = ["net", "thread"], optional = true } +thiserror = { version = "1.0.26", optional = true } +thiserror_sgx = { package = "thiserror", git = "https://github.com/mesalock-linux/thiserror-sgx", tag = "sgx_1.1.3", optional = true } +url_sgx = { package = "url", git = "https://github.com/mesalock-linux/rust-url-sgx", tag = "sgx_1.1.3", optional = true } + +# no_std dependencies +codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } +lazy_static = { version = "1.1.0", features = ["spin_no_std"] } +log = { version = "0.4", default-features = false } +serde = { version = "1.0", default-features = false, features = ["derive", "alloc"] } +serde_json = { version = "1.0", default-features = false, features = ["alloc"] } + +# internal dependencies +ita-stf = { path = "../../../app-libs/stf", default-features = false } +itc-rest-client = { path = "../../../core/rest-client", default-features = false } +itp-ocall-api = { path = "../../../core-primitives/ocall-api", default-features = false } +itp-sgx-crypto = { path = "../../../core-primitives/sgx/crypto", default-features = false } +itp-sgx-externalities = { path = "../../../core-primitives/substrate-sgx/externalities", default-features = false } +itp-storage = { path = "../../../core-primitives/storage", default-features = false } +itp-types = { path = "../../../core-primitives/types", default-features = false } +itp-utils = { path = "../../../core-primitives/utils", default-features = false } + +frame-support = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.29", default-features = false } +sp-core = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.29", default-features = false } +sp-io = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.29", default-features = false } +sp-std = { git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.29", default-features = false } + +lc-data-providers = { path = "../data-providers", default-features = false } +lc-stf-task-sender = { path = "../stf-task/sender", default-features = false } +litentry-primitives = { path = "../../primitives", default-features = false } + +[features] +default = ["std"] +sgx = [ + "futures_sgx", + "hex-sgx", + "http-sgx", + "http_req-sgx", + "itc-rest-client/sgx", + "sgx_tstd", + "thiserror_sgx", + "url_sgx", + "ita-stf/sgx", + "itp-sgx-externalities/sgx", + "itp-sgx-crypto/sgx", + "lc-data-providers/sgx", + "litentry-primitives/sgx", + "lc-stf-task-sender/sgx", +] +std = [ + "futures", + "hex", + "http", + "http_req", + "itc-rest-client/std", + "log/std", + "serde/std", + "serde_json/std", + "thiserror", + "url", + "ita-stf/std", + "itp-types/std", + "itp-utils/std", + "itp-sgx-externalities/std", + "itp-storage/std", + "itp-sgx-crypto/std", + "sp-std/std", + "sp-io/std", + "frame-support/std", + "lc-data-providers/std", + "litentry-primitives/std", + "lc-stf-task-sender/std", +] diff --git a/tee-worker/litentry/core/identity-verification/src/error.rs b/tee-worker/litentry/core/identity-verification/src/error.rs new file mode 100644 index 0000000000..2f727f730a --- /dev/null +++ b/tee-worker/litentry/core/identity-verification/src/error.rs @@ -0,0 +1,44 @@ +// Copyright 2020-2022 Litentry Technologies GmbH. +// This file is part of Litentry. +// +// Litentry is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Litentry is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Litentry. If not, see . + +#[cfg(all(not(feature = "std"), feature = "sgx"))] +use crate::sgx_reexport_prelude::*; +use std::boxed::Box; + +pub type Result = core::result::Result; + +// identity verification errors +#[derive(Debug, thiserror::Error)] +pub enum Error { + #[error("unexpected message")] + UnexpectedMessage, + #[error("wrong identity handle type")] + WrongIdentityHanldeType, + #[error("wrong signature type")] + WrongSignatureType, + #[error("wrong web3 network type")] + WrongWeb3NetworkType, + #[error("failed to verify substrate signature")] + VerifySubstrateSignatureFailed, + #[error("failed to recover substrate public key")] + RecoverSubstratePubkeyFailed, + #[error("failed to verify evm signature")] + VerifyEvmSignatureFailed, + #[error("failed to recover evm address")] + RecoverEvmAddressFailed, + #[error(transparent)] + Other(#[from] Box), +} diff --git a/tee-worker/litentry/core/identity-verification/src/lib.rs b/tee-worker/litentry/core/identity-verification/src/lib.rs new file mode 100644 index 0000000000..0fa022be2c --- /dev/null +++ b/tee-worker/litentry/core/identity-verification/src/lib.rs @@ -0,0 +1,57 @@ +// Copyright 2020-2022 Litentry Technologies GmbH. +// This file is part of Litentry. +// +// Litentry is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Litentry is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Litentry. If not, see . + +#![cfg_attr(not(feature = "std"), no_std)] + +#[cfg(all(not(feature = "std"), feature = "sgx"))] +extern crate sgx_tstd as std; + +// re-export module to properly feature gate sgx and regular std environment +#[cfg(all(not(feature = "std"), feature = "sgx"))] +pub mod sgx_reexport_prelude { + pub use futures_sgx as futures; + pub use hex_sgx as hex; + pub use http_req_sgx as http_req; + pub use http_sgx as http; + pub use thiserror_sgx as thiserror; + pub use url_sgx as url; +} + +#[cfg(all(feature = "std", feature = "sgx"))] +compile_error!("feature \"std\" and feature \"sgx\" cannot be enabled at the same time"); + +use codec::Encode; +use frame_support::pallet_prelude::*; +use sp_core::blake2_256; +// this should be ita_stf::AccountId, but we use itp_types to avoid cyclic dep +use itp_types::AccountId; +use litentry_primitives::{ChallengeCode, Identity}; +use sp_std::vec::Vec; +use std::string::ToString; + +pub mod web2; +pub mod web3; + +pub mod error; + +// verification message format: + + , +// where <> means SCALE-encoded +pub fn get_expected_payload(who: &AccountId, identity: &Identity, code: &ChallengeCode) -> Vec { + let mut payload = code.encode(); + payload.append(&mut who.encode()); + payload.append(&mut identity.encode()); + blake2_256(payload.as_slice()).to_vec() +} diff --git a/tee-worker/litentry/core/identity-verification/src/web2/mod.rs b/tee-worker/litentry/core/identity-verification/src/web2/mod.rs new file mode 100644 index 0000000000..544985e4c7 --- /dev/null +++ b/tee-worker/litentry/core/identity-verification/src/web2/mod.rs @@ -0,0 +1,140 @@ +// Copyright 2020-2022 Litentry Technologies GmbH. +// This file is part of Litentry. +// +// Litentry is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Litentry is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Litentry. If not, see . + +#[cfg(all(not(feature = "std"), feature = "sgx"))] +extern crate sgx_tstd as std; + +#[cfg(all(not(feature = "std"), feature = "sgx"))] +use crate::sgx_reexport_prelude::*; + +#[cfg(all(feature = "std", feature = "sgx"))] +compile_error!("feature \"std\" and feature \"sgx\" cannot be enabled at the same time"); + +use crate::{ensure, get_expected_payload}; +use codec::{Decode, Encode}; +use std::{ + fmt::Debug, + format, + string::{String, ToString}, + vec::Vec, +}; + +use itp_sgx_crypto::ShieldingCryptoDecrypt; +use lc_data_providers::{ + discord_official::{DiscordMessage, DiscordOfficialClient}, + twitter_official::{Tweet, TwitterOfficialClient}, + UserInfo, +}; +use lc_stf_task_sender::Web2IdentityVerificationRequest; +use litentry_primitives::{ + DiscordValidationData, IdentityHandle, TwitterValidationData, Web2ValidationData, +}; + +// TODO: maybe split this file into smaller mods +#[derive(Debug, thiserror::Error, Clone)] +pub enum Error { + #[error("Request error: {0}")] + RequestError(String), + + #[error("Other error: {0}")] + OtherError(String), +} + +#[derive(Encode, Decode, Clone, Debug, PartialEq, Eq)] +pub struct Web2IdentityVerification { + pub verification_request: Web2IdentityVerificationRequest, +} + +pub trait DecryptionVerificationPayload { + fn decrypt_ciphertext(&self, key: K) -> Result, Error>; +} + +fn payload_from_tweet(tweet: &Tweet) -> Result, Error> { + if tweet.text.starts_with("0x") { + let bytes = &tweet.text.as_bytes()[b"0x".len()..]; + hex::decode(bytes).map_err(|e| Error::OtherError(format!("Hex error: {:?}", e))) + } else { + hex::decode(tweet.text.as_bytes()) + .map_err(|e| Error::OtherError(format!("Hex error: {:?}", e))) + } +} + +fn payload_from_discord(discord: &DiscordMessage) -> Result, Error> { + let data = &discord.content; + if data.starts_with("0x") { + let bytes = &data.as_bytes()[b"0x".len()..]; + hex::decode(bytes).map_err(|e| Error::OtherError(format!("Hex error: {:?}", e))) + } else { + hex::decode(data.as_bytes()).map_err(|e| Error::OtherError(format!("Hex error: {:?}", e))) + } +} + +pub fn verify(request: Web2IdentityVerificationRequest) -> Result<(), Error> { + let (user_id, payload) = match request.validation_data { + Web2ValidationData::Twitter(TwitterValidationData { ref tweet_id }) => { + let mut client = TwitterOfficialClient::new(); + let tweet: Tweet = client + .query_tweet(tweet_id.to_vec()) + .map_err(|e| Error::RequestError(format!("{:?}", e)))?; + + let user_id = tweet + .get_user_id() + .ok_or_else(|| Error::OtherError("can not find user_id".to_string()))?; + + let payload = payload_from_tweet(&tweet)?; + + Ok((user_id, payload)) + }, + Web2ValidationData::Discord(DiscordValidationData { + ref channel_id, + ref message_id, + .. + }) => { + let mut client = DiscordOfficialClient::new(); + let message: DiscordMessage = client + .query_message(channel_id.to_vec(), message_id.to_vec()) + .map_err(|e| Error::RequestError(format!("{:?}", e)))?; + + let user_id = message + .get_user_id() + .ok_or_else(|| Error::OtherError("can not find user_id".to_string()))?; + + let payload = payload_from_discord(&message)?; + + Ok((user_id, payload)) + }, + }?; + + // the user_id must match, is it case sensitive? + match request.identity.handle { + IdentityHandle::String(ref handle) => { + let handle = std::str::from_utf8(handle.as_slice()) + .map_err(|_| Error::OtherError("convert IdentityHandle error".to_string()))?; + if !user_id.eq(handle) { + return Err(Error::OtherError("user_id not match".to_string())) + } + }, + _ => return Err(Error::OtherError("IdentityHandle not support".to_string())), + } + + // the payload must match + // TODO: maybe move it to common place + ensure!( + payload == get_expected_payload(&request.who, &request.identity, &request.challenge_code), + Error::OtherError("payload not match".to_string()) + ); + Ok(()) +} diff --git a/tee-worker/litentry/core/identity-verification/src/web3/mod.rs b/tee-worker/litentry/core/identity-verification/src/web3/mod.rs new file mode 100644 index 0000000000..faf476e002 --- /dev/null +++ b/tee-worker/litentry/core/identity-verification/src/web3/mod.rs @@ -0,0 +1,142 @@ +// Copyright 2020-2022 Litentry Technologies GmbH. +// This file is part of Litentry. +// +// Litentry is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Litentry is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Litentry. If not, see . + +use crate::{ + ensure, + error::{Error, Result}, + get_expected_payload, AccountId, ToString, +}; +use litentry_primitives::{ + ChallengeCode, Identity, IdentityHandle, IdentityMultiSignature, IdentityWebType, + Web3CommonValidationData, Web3Network, Web3ValidationData, +}; +use sp_core::{ed25519, sr25519}; +use sp_io::{ + crypto::{ + ed25519_verify, secp256k1_ecdsa_recover, secp256k1_ecdsa_recover_compressed, sr25519_verify, + }, + hashing::{blake2_256, keccak_256}, +}; + +pub fn verify( + who: AccountId, + identity: Identity, + code: ChallengeCode, + web3: Web3ValidationData, +) -> Result<()> { + match web3 { + Web3ValidationData::Substrate(substrate_validation_data) => + verify_substrate_signature(&who, &identity, &code, &substrate_validation_data), + Web3ValidationData::Evm(evm_validation_data) => + verify_evm_signature(&who, &identity, &code, &evm_validation_data), + } +} + +fn verify_substrate_signature( + who: &AccountId, + identity: &Identity, + code: &ChallengeCode, + validation_data: &Web3CommonValidationData, +) -> Result<()> { + let msg = get_expected_payload(who, identity, code); + + ensure!(msg.as_slice() == validation_data.message.as_slice(), Error::UnexpectedMessage); + + let substrate_address = match &identity.web_type { + IdentityWebType::Web3(Web3Network::Substrate(_)) => match &identity.handle { + IdentityHandle::Address32(handle) => handle, + _ => return Err(Error::WrongIdentityHanldeType), + }, + _ => return Err(Error::WrongWeb3NetworkType), + }; + + match &validation_data.signature { + IdentityMultiSignature::Sr25519(sig) => { + ensure!( + sr25519_verify(sig, &msg, &sr25519::Public(*substrate_address)), + Error::VerifySubstrateSignatureFailed + ); + }, + IdentityMultiSignature::Ed25519(sig) => { + ensure!( + ed25519_verify(sig, &msg, &ed25519::Public(*substrate_address)), + Error::VerifySubstrateSignatureFailed + ); + }, + // We can' use `ecdsa_verify` directly we don't have the raw 33-bytes publick key + // instead we only have AccountId which is blake2_256(pubkey) + IdentityMultiSignature::Ecdsa(sig) => { + // see https://github.com/paritytech/substrate/blob/493b58bd4a475080d428ce47193ee9ea9757a808/primitives/runtime/src/traits.rs#L132 + let digest = blake2_256(&msg); + let recovered_substrate_pubkey = secp256k1_ecdsa_recover_compressed(&sig.0, &digest) + .map_err(|_| Error::RecoverSubstratePubkeyFailed)?; + ensure!( + &blake2_256(&recovered_substrate_pubkey) == substrate_address, + Error::VerifySubstrateSignatureFailed + ); + }, + _ => return Err(Error::WrongSignatureType), + } + Ok(()) +} + +fn verify_evm_signature( + who: &AccountId, + identity: &Identity, + code: &ChallengeCode, + validation_data: &Web3CommonValidationData, +) -> Result<()> { + let msg = get_expected_payload(who, identity, code); + let digest = compute_evm_msg_digest(&msg); + if let IdentityMultiSignature::Ethereum(sig) = &validation_data.signature { + let recovered_evm_address = recover_evm_address(&digest, sig.as_ref()) + .map_err(|_| Error::RecoverEvmAddressFailed)?; + let evm_address = match &identity.web_type { + IdentityWebType::Web3(Web3Network::Evm(_)) => match &identity.handle { + IdentityHandle::Address20(handle) => handle, + _ => return Err(Error::WrongIdentityHanldeType), + }, + _ => return Err(Error::WrongWeb3NetworkType), + }; + ensure!(&recovered_evm_address == evm_address, Error::VerifyEvmSignatureFailed); + } else { + return Err(Error::WrongSignatureType) + } + Ok(()) +} + +// we use an EIP-191 message has computing +fn compute_evm_msg_digest(message: &[u8]) -> [u8; 32] { + let eip_191_message = [ + "\x19Ethereum Signed Message:\n".as_bytes(), + message.len().to_string().as_bytes(), + message, + ] + .concat(); + keccak_256(&eip_191_message) +} + +fn recover_evm_address( + msg: &[u8; 32], + sig: &[u8; 65], +) -> core::result::Result<[u8; 20], sp_io::EcdsaVerifyError> { + let pubkey = secp256k1_ecdsa_recover(sig, msg)?; + let hashed_pk = keccak_256(&pubkey); + + let mut addr = [0u8; 20]; + addr[..20].copy_from_slice(&hashed_pk[12..32]); + Ok(addr) +} diff --git a/tee-worker/litentry/core/mock-server/Cargo.toml b/tee-worker/litentry/core/mock-server/Cargo.toml new file mode 100644 index 0000000000..845020883e --- /dev/null +++ b/tee-worker/litentry/core/mock-server/Cargo.toml @@ -0,0 +1,21 @@ +[package] +edition = "2021" +name = "lc-mock-server" +version = "0.1.0" + +[dependencies] +hex = "0.4.3" +httpmock = "0.6" +isahc = "1.7" +lazy_static = "1.4" +serde_json = { version = "1.0", default-features = false, features = ["alloc"] } +tokio = { version = "1.5", features = ["sync", "macros", "rt-multi-thread", "signal"] } + +codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } +litentry-primitives = { path = "../../primitives", default-features = false } +sp-core = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.29", default-features = false } + +lc-data-providers = { path = "../data-providers" } + +[features] +mockserver = [] diff --git a/tee-worker/litentry/core/mock-server/src/discord_litentry.rs b/tee-worker/litentry/core/mock-server/src/discord_litentry.rs new file mode 100644 index 0000000000..ddd024ba65 --- /dev/null +++ b/tee-worker/litentry/core/mock-server/src/discord_litentry.rs @@ -0,0 +1,85 @@ +// Copyright 2020-2022 Litentry Technologies GmbH. +// This file is part of Litentry. +// +// Litentry is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Litentry is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Litentry. If not, see . + +use httpmock::{Method::GET, MockServer}; +use lc_data_providers::discord_litentry::DiscordResponse; + +use crate::Mock; + +pub trait DiscordLitentryAPI { + fn check_join(mock_server: &MockServer); + fn check_id_hubber(mock_server: &MockServer); +} + +pub struct DiscordLitentry {} +impl DiscordLitentry { + pub fn new() -> Self { + DiscordLitentry {} + } +} + +impl Default for DiscordLitentry { + fn default() -> Self { + Self::new() + } +} + +impl DiscordLitentryAPI for DiscordLitentry { + fn check_join(mock_server: &MockServer) { + let body = DiscordResponse { + data: true, + message: "success".into(), + has_errors: false, + msg_code: 200, + success: true, + }; + + let path = "/discord/joined"; + mock_server.mock(|when, then| { + when.method(GET) + .path(path) + .query_param("guildid", "919848390156767232") + .query_param("handler", "againstwar#4779"); + then.status(200).body(serde_json::to_string(&body).unwrap()); + }); + } + + fn check_id_hubber(mock_server: &MockServer) { + let body = DiscordResponse { + data: true, + message: "success".into(), + has_errors: false, + msg_code: 200, + success: true, + }; + + mock_server.mock(|when, then| { + when.method(GET) + .path("/discord/commented/idhubber") + .query_param("guildid", "919848390156767232") + .query_param("handler", "ericzhang.eth#0114"); + + then.status(200).body(serde_json::to_string(&body).unwrap()); + }); + } +} + +impl Mock for DiscordLitentry { + fn mock(&self, mock_server: &httpmock::MockServer) { + DiscordLitentry::check_join(mock_server); + DiscordLitentry::check_id_hubber(mock_server); + } +} diff --git a/tee-worker/litentry/core/mock-server/src/discord_official.rs b/tee-worker/litentry/core/mock-server/src/discord_official.rs new file mode 100644 index 0000000000..d7f1767785 --- /dev/null +++ b/tee-worker/litentry/core/mock-server/src/discord_official.rs @@ -0,0 +1,67 @@ +// Copyright 2020-2022 Litentry Technologies GmbH. +// This file is part of Litentry. +// +// Litentry is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Litentry is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Litentry. If not, see . + +use httpmock::{Method::GET, MockServer}; +use lc_data_providers::discord_official::{DiscordMessage, DiscordMessageAuthor}; + +use crate::Mock; + +pub trait DiscordOfficialAPI { + fn query_message(mock_server: &MockServer); +} + +pub struct DiscordOfficial {} +impl DiscordOfficial { + pub fn new() -> Self { + DiscordOfficial {} + } +} + +impl Default for DiscordOfficial { + fn default() -> Self { + Self::new() + } +} + +impl DiscordOfficialAPI for DiscordOfficial { + fn query_message(mock_server: &MockServer) { + let channel_id = "919848392035794945"; + let message_id = "1"; + + let user_id = "001"; + let username = "elon"; + let author = DiscordMessageAuthor { id: user_id.into(), username: username.into() }; + + let body = DiscordMessage { + id: message_id.into(), + channel_id: channel_id.into(), + content: "Hello, litentry.".into(), + author, + }; + + let path = format! {"/api/channels/{}/messages/{}", channel_id, message_id}; + mock_server.mock(|when, then| { + when.method(GET).path(path); + then.status(200).body(serde_json::to_string(&body).unwrap()); + }); + } +} + +impl Mock for DiscordOfficial { + fn mock(&self, mock_server: &httpmock::MockServer) { + DiscordOfficial::query_message(mock_server); + } +} diff --git a/tee-worker/litentry/core/mock-server/src/lib.rs b/tee-worker/litentry/core/mock-server/src/lib.rs new file mode 100644 index 0000000000..d02279dfba --- /dev/null +++ b/tee-worker/litentry/core/mock-server/src/lib.rs @@ -0,0 +1,101 @@ +// Copyright 2020-2022 Litentry Technologies GmbH. +// This file is part of Litentry. +// +// Litentry is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Litentry is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Litentry. If not, see . + +#[macro_use] +extern crate lazy_static; + +use std::{ + sync::Mutex, + thread::{spawn, JoinHandle}, +}; + +use codec::Encode; +use httpmock::{standalone::start_standalone_server, MockServer}; +use litentry_primitives::{ChallengeCode, Identity}; +use sp_core::{blake2_256, crypto::AccountId32 as AccountId}; +use tokio::task::LocalSet; + +pub mod discord_litentry; +pub mod discord_official; +pub mod twitter_litentry; +pub mod twitter_official; + +pub use discord_litentry::*; +pub use discord_official::*; +pub use twitter_litentry::*; +pub use twitter_official::*; +pub fn standalone_server() { + let _server = STANDALONE_SERVER.lock().unwrap_or_else(|e| e.into_inner()); +} + +lazy_static! { + static ref STANDALONE_SERVER: Mutex>> = Mutex::new(spawn(|| { + let srv = start_standalone_server(9527, false, None, false, usize::MAX); + let runtime = tokio::runtime::Builder::new_current_thread().enable_all().build().unwrap(); + LocalSet::new().block_on(&runtime, srv) + })); +} + +pub fn mock_tweet_payload(who: &AccountId, identity: &Identity, code: &ChallengeCode) -> String { + let mut payload = code.encode(); + payload.append(&mut who.encode()); + payload.append(&mut identity.encode()); + hex::encode(blake2_256(payload.as_slice())) +} + +pub trait Mock { + fn mock(&self, mock_server: &MockServer); +} + +struct MockServerManager { + servers: Vec>, + mock_server: MockServer, +} +impl MockServerManager { + pub fn new() -> Self { + let mock_server = MockServer::connect("localhost:9527"); + MockServerManager { servers: vec![], mock_server } + } + + pub fn register(&mut self, server: Box) { + self.servers.push(server); + } + + pub fn run(&self) { + for server in &self.servers { + server.mock(&self.mock_server); + } + } +} +pub fn run() { + standalone_server(); + + let mut mock_server_manager = MockServerManager::new(); + + let discord_litentry = Box::new(DiscordLitentry::new()); + mock_server_manager.register(discord_litentry); + + let discord_official = Box::new(DiscordOfficial::new()); + mock_server_manager.register(discord_official); + + let twitter_litentry = Box::new(TwitterLitentry::new()); + mock_server_manager.register(twitter_litentry); + + let twitter_official = Box::new(TwitterOfficial::new()); + mock_server_manager.register(twitter_official); + + mock_server_manager.run(); +} diff --git a/tee-worker/litentry/core/mock-server/src/twitter_litentry.rs b/tee-worker/litentry/core/mock-server/src/twitter_litentry.rs new file mode 100644 index 0000000000..e2e3e7b8be --- /dev/null +++ b/tee-worker/litentry/core/mock-server/src/twitter_litentry.rs @@ -0,0 +1,56 @@ +// Copyright 2020-2022 Litentry Technologies GmbH. +// This file is part of Litentry. +// +// Litentry is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Litentry is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Litentry. If not, see . + +use httpmock::{Method::GET, MockServer}; + +use crate::Mock; + +pub trait TwitterLitentryAPI { + fn check_follow(mock_server: &MockServer); +} + +pub struct TwitterLitentry {} +impl TwitterLitentry { + pub fn new() -> Self { + TwitterLitentry {} + } +} + +impl Default for TwitterLitentry { + fn default() -> Self { + Self::new() + } +} + +impl TwitterLitentryAPI for TwitterLitentry { + fn check_follow(mock_server: &MockServer) { + let body = r#"{ "data": false }"#; + let path = "/twitter/followers/verification"; + mock_server.mock(|when, then| { + when.method(GET) + .path(path) + .query_param("handler1", "litentry") + .query_param("handler2", "ericzhangeth"); + then.status(200).body(body); + }); + } +} + +impl Mock for TwitterLitentry { + fn mock(&self, mock_server: &httpmock::MockServer) { + TwitterLitentry::check_follow(mock_server); + } +} diff --git a/tee-worker/litentry/core/mock-server/src/twitter_official.rs b/tee-worker/litentry/core/mock-server/src/twitter_official.rs new file mode 100644 index 0000000000..7cd4e843d0 --- /dev/null +++ b/tee-worker/litentry/core/mock-server/src/twitter_official.rs @@ -0,0 +1,142 @@ +// Copyright 2020-2022 Litentry Technologies GmbH. +// This file is part of Litentry. +// +// Litentry is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Litentry is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Litentry. If not, see . + +use httpmock::{Method::GET, MockServer}; +use lc_data_providers::twitter_official::*; +use litentry_primitives::{ + ChallengeCode, Identity, IdentityHandle, IdentityString, IdentityWebType, Web2Network, +}; +use sp_core::crypto::AccountId32 as AccountId; + +use crate::{mock_tweet_payload, Mock}; + +pub trait TwitterOfficialAPI { + fn query_tweet(mock_server: &MockServer); + fn query_retweet(mock_server: &MockServer); + fn query_user(mock_server: &MockServer); +} + +pub struct TwitterOfficial {} +impl TwitterOfficial { + pub fn new() -> Self { + TwitterOfficial {} + } +} + +impl Default for TwitterOfficial { + fn default() -> Self { + Self::new() + } +} + +impl TwitterOfficialAPI for TwitterOfficial { + fn query_tweet(mock_server: &MockServer) { + let tweet_id = "100"; + + let account_id = AccountId::new([ + 212, 53, 147, 199, 21, 253, 211, 28, 97, 20, 26, 189, 4, 169, 159, 214, 130, 44, 133, + 88, 133, 76, 205, 227, 154, 86, 132, 231, 165, 109, 162, 125, + ]); // Alice + let twitter_identity = Identity { + web_type: IdentityWebType::Web2(Web2Network::Twitter), + handle: IdentityHandle::String( + IdentityString::try_from("mock_user".as_bytes().to_vec()).unwrap(), + ), + }; + let chanllenge_code: ChallengeCode = + [8, 104, 90, 56, 35, 213, 18, 250, 213, 210, 119, 241, 2, 174, 24, 8]; + let payload = mock_tweet_payload(&account_id, &twitter_identity, &chanllenge_code); + + let tweet = Tweet { author_id: "mock_user".into(), id: tweet_id.into(), text: payload }; + + let path = format! {"/2/tweets/{}", tweet_id}; + mock_server.mock(|when, then| { + when.method(GET) + .path(path) + .query_param("ids", tweet_id) + .query_param("expansions", "author_id"); + then.status(200).body(serde_json::to_string(&tweet).unwrap()); + }); + } + + fn query_retweet(mock_server: &MockServer) { + let author_id = "ericzhangeth"; + let id = "100"; + + let account_id = AccountId::new([0u8; 32]); + let twitter_identity = Identity { + web_type: IdentityWebType::Web2(Web2Network::Twitter), + handle: IdentityHandle::String( + IdentityString::try_from("litentry".as_bytes().to_vec()).unwrap(), + ), + }; + let chanllenge_code: ChallengeCode = + [8, 104, 90, 56, 35, 213, 18, 250, 213, 210, 119, 241, 2, 174, 24, 8]; + let payload = mock_tweet_payload(&account_id, &twitter_identity, &chanllenge_code); + + let tweets = vec![Tweet { + author_id: author_id.into(), + id: id.into(), + text: serde_json::to_string(&payload).unwrap(), + }]; + let body = TwitterAPIV2Response { data: Some(tweets), meta: None }; + + let path = "/2/tweets/search/recent"; + + let user = "ericzhangeth"; + let original_tweet_id = "100"; + let query_value = format!("from: {} retweets_of_tweet_id: {}", user, original_tweet_id); + + mock_server.mock(|when, then| { + when.method(GET) + .path(path) + .query_param("query", query_value) + .query_param("expansions", "author_id"); + then.status(200).body(serde_json::to_string(&body).unwrap()); + }); + } + + fn query_user(mock_server: &MockServer) { + let user = "1256908613857226756"; + + let twitter_user_data = TwitterUser { + id: user.into(), + name: "ericzhang".into(), + username: "elon".into(), + public_metrics: TwitterUserPublicMetrics { + followers_count: 100_u32, + following_count: 99_u32, + }, + }; + + let body = TwitterAPIV2Response { data: Some(twitter_user_data), meta: None }; + + let path = format! {"/2/users/{}", user}; + + mock_server.mock(|when, then| { + when.method(GET).path(path).query_param("user.fields", "public_metrics"); + then.status(200).body(serde_json::to_string(&body).unwrap()); + }); + } +} + +impl Mock for TwitterOfficial { + fn mock(&self, mock_server: &httpmock::MockServer) { + TwitterOfficial::query_tweet(mock_server); + TwitterOfficial::query_retweet(mock_server); + TwitterOfficial::query_user(mock_server); + } +} diff --git a/tee-worker/litentry/core/stf-task/receiver/Cargo.toml b/tee-worker/litentry/core/stf-task/receiver/Cargo.toml new file mode 100644 index 0000000000..b388e1be70 --- /dev/null +++ b/tee-worker/litentry/core/stf-task/receiver/Cargo.toml @@ -0,0 +1,111 @@ +[package] +authors = ["Litentry Dev"] +edition = "2021" +name = "lc-stf-task-receiver" +version = "0.1.0" + +[dependencies] +# std dependencies +futures = { version = "0.3.8", optional = true } +hex = { version = "0.4.3", optional = true } +http = { version = "0.2", optional = true } +http_req = { optional = true, features = ["rust-tls"], branch = "master", git = "https://github.com/integritee-network/http_req" } +thiserror = { version = "1.0.26", optional = true } +url = { version = "2.0.0", optional = true } + +# sgx dependencies +futures_sgx = { package = "futures", git = "https://github.com/mesalock-linux/futures-rs-sgx", optional = true } +hex-sgx = { package = "hex", git = "https://github.com/mesalock-linux/rust-hex-sgx", tag = "sgx_1.1.3", features = ["sgx_tstd"], optional = true } +http-sgx = { package = "http", git = "https://github.com/integritee-network/http-sgx.git", branch = "sgx-experimental", optional = true } +http_req-sgx = { package = "http_req", git = "https://github.com/integritee-network/http_req", default-features = false, features = ["rust-tls", "sgx"], optional = true } +sgx_tstd = { git = "https://github.com/apache/teaclave-sgx-sdk.git", branch = "master", features = ["net", "thread"], optional = true } +thiserror_sgx = { package = "thiserror", git = "https://github.com/mesalock-linux/thiserror-sgx", tag = "sgx_1.1.3", optional = true } +url_sgx = { package = "url", git = "https://github.com/mesalock-linux/rust-url-sgx", tag = "sgx_1.1.3", optional = true } + +# no_std dependencies +codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } +lazy_static = { version = "1.1.0", features = ["spin_no_std"] } +log = { version = "0.4", default-features = false } +serde = { version = "1.0", default-features = false, features = ["derive", "alloc"] } +serde_json = { version = "1.0", default-features = false, features = ["alloc"] } +sp-core = { default-features = false, features = ["full_crypto"], git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.29" } + +# internal dependencies +ita-stf = { path = "../../../../app-libs/stf", default-features = false } +itc-rest-client = { path = "../../../../core/rest-client", default-features = false } +itp-types = { path = "../../../../core-primitives/types", default-features = false } + +itp-extrinsics-factory = { path = "../../../../core-primitives/extrinsics-factory", default-features = false } +itp-ocall-api = { path = "../../../../core-primitives/ocall-api", default-features = false } +itp-sgx-crypto = { path = "../../../../core-primitives/sgx/crypto", default-features = false } +itp-sgx-externalities = { path = "../../../../core-primitives/substrate-sgx/externalities", default-features = false } +itp-stf-executor = { path = "../../../../core-primitives/stf-executor", default-features = false } +itp-stf-state-handler = { path = "../../../../core-primitives/stf-state-handler", default-features = false } +itp-stf-state-observer = { path = "../../../../core-primitives/stf-state-observer", default-features = false } +itp-storage = { path = "../../../../core-primitives/storage", default-features = false } +itp-top-pool-author = { path = "../../../../core-primitives/top-pool-author", default-features = false } +itp-utils = { path = "../../../../core-primitives/utils", default-features = false } + +# litentry +frame-support = { default-features = false, git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.29" } +ita-sgx-runtime = { path = "../../../../app-libs/sgx-runtime", default-features = false } +lc-assertion-build = { path = "../../assertion-build", default-features = false } +lc-identity-verification = { path = "../../identity-verification", default-features = false } +lc-stf-task-sender = { path = "../sender", default-features = false } +litentry-primitives = { path = "../../../primitives", default-features = false } +sp-std = { git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.29", default-features = false } + +[features] +default = ["std"] +mockserver = [ + "lc-assertion-build/mockserver", +] +sgx = [ + "futures_sgx", + "hex-sgx", + "http-sgx", + "http_req-sgx", + "itc-rest-client/sgx", + "sgx_tstd", + "thiserror_sgx", + "url_sgx", + "ita-stf/sgx", + "itp-sgx-externalities/sgx", + "itp-stf-executor/sgx", + "itp-stf-state-handler/sgx", + "itp-top-pool-author/sgx", + "itp-stf-state-observer/sgx", + "itp-utils/sgx", + "sp-core/full_crypto", + "litentry-primitives/sgx", + "lc-stf-task-sender/sgx", + "lc-identity-verification/sgx", + "lc-assertion-build/sgx", +] +std = [ + "futures", + "hex", + "http", + "http_req", + "itc-rest-client/std", + "log/std", + "serde/std", + "serde_json/std", + "thiserror", + "url", + "itp-types/std", + "itp-utils/std", + "itp-top-pool-author/std", + "itp-storage/std", + "itp-stf-executor/std", + "itp-stf-state-handler/std", + "itp-stf-state-observer/std", + "sp-core/std", + "litentry-primitives/std", + "lc-stf-task-sender/std", + "lc-identity-verification/std", + "lc-assertion-build/std", + "ita-sgx-runtime/std", + "frame-support/std", + "sp-std/std", +] diff --git a/tee-worker/litentry/core/stf-task/receiver/src/lib.rs b/tee-worker/litentry/core/stf-task/receiver/src/lib.rs new file mode 100644 index 0000000000..2f5de70067 --- /dev/null +++ b/tee-worker/litentry/core/stf-task/receiver/src/lib.rs @@ -0,0 +1,132 @@ +// Copyright 2020-2022 Litentry Technologies GmbH. +// This file is part of Litentry. +// +// Litentry is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Litentry is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Litentry. If not, see . + +#![cfg_attr(not(feature = "std"), no_std)] + +#[cfg(all(not(feature = "std"), feature = "sgx"))] +extern crate sgx_tstd as std; + +// re-export module to properly feature gate sgx and regular std environment +#[cfg(all(not(feature = "std"), feature = "sgx"))] +pub mod sgx_reexport_prelude { + pub use futures_sgx as futures; + pub use hex_sgx as hex; + pub use thiserror_sgx as thiserror; + pub use url_sgx as url; +} + +#[cfg(all(not(feature = "std"), feature = "sgx"))] +use crate::sgx_reexport_prelude::*; + +#[cfg(all(feature = "std", feature = "sgx"))] +compile_error!("feature \"std\" and feature \"sgx\" cannot be enabled at the same time"); + +use codec::{Decode, Encode}; +use futures::executor; +use ita_stf::{Hash, ShardIdentifier, TrustedCall, TrustedOperation}; +use itp_sgx_crypto::{ShieldingCryptoDecrypt, ShieldingCryptoEncrypt}; +use itp_sgx_externalities::SgxExternalitiesTrait; +use itp_stf_executor::traits::StfEnclaveSigning; +use itp_stf_state_handler::handle_state::HandleState; +use itp_top_pool_author::traits::AuthorApi; +use sp_std::vec::Vec; +use std::{format, string::String, sync::Arc}; + +#[derive(Debug, thiserror::Error, Clone)] +pub enum Error { + #[error("Request error: {0}")] + RequestError(String), + + #[error("Assertion error: {0}")] + AssertionError(String), + + #[error("Other error: {0}")] + OtherError(String), +} + +pub mod stf_task_receiver; + +#[allow(dead_code)] +pub struct StfTaskContext< + K: ShieldingCryptoDecrypt + ShieldingCryptoEncrypt + Clone, + A: AuthorApi, + S: StfEnclaveSigning, + H: HandleState, +> { + shielding_key: K, + author_api: Arc, + enclave_signer: Arc, + pub state_handler: Arc, +} + +impl< + K: ShieldingCryptoDecrypt + ShieldingCryptoEncrypt + Clone, + A: AuthorApi, + S: StfEnclaveSigning, + H: HandleState, + > StfTaskContext +where + H::StateT: SgxExternalitiesTrait, +{ + pub fn new( + shielding_key: K, + author_api: Arc, + enclave_signer: Arc, + state_handler: Arc, + ) -> Self { + Self { shielding_key, author_api, enclave_signer, state_handler } + } + + pub fn decode_and_submit_trusted_call( + &self, + encoded_shard: Vec, + encoded_callback: Vec, + ) -> Result<(), Error> { + let shard = ShardIdentifier::decode(&mut encoded_shard.as_slice()) + .map_err(|e| Error::OtherError(format!("error decoding ShardIdentifier {:?}", e)))?; + let callback = TrustedCall::decode(&mut encoded_callback.as_slice()) + .map_err(|e| Error::OtherError(format!("error decoding TrustedCall {:?}", e)))?; + self.submit_trusted_call(&shard, &callback) + } + + fn submit_trusted_call( + &self, + shard: &ShardIdentifier, + trusted_call: &TrustedCall, + ) -> Result<(), Error> { + let signed_trusted_call = self + .enclave_signer + .sign_call_with_self(trusted_call, shard) + .map_err(|e| Error::OtherError(format!("{:?}", e)))?; + + let trusted_operation = TrustedOperation::indirect_call(signed_trusted_call); + + let encrypted_trusted_call = self + .shielding_key + .encrypt(&trusted_operation.encode()) + .map_err(|e| Error::OtherError(format!("{:?}", e)))?; + + let top_submit_future = + async { self.author_api.submit_top(encrypted_trusted_call, *shard).await }; + executor::block_on(top_submit_future).map_err(|e| { + Error::OtherError(format!("Error adding indirect trusted call to TOP pool: {:?}", e)) + })?; + + Ok(()) + } + + // TODO: maybe add a wrapper to read the state and eliminate the public access to `state_handler` +} diff --git a/tee-worker/litentry/core/stf-task/receiver/src/stf_task_receiver.rs b/tee-worker/litentry/core/stf-task/receiver/src/stf_task_receiver.rs new file mode 100644 index 0000000000..0fa245cbee --- /dev/null +++ b/tee-worker/litentry/core/stf-task/receiver/src/stf_task_receiver.rs @@ -0,0 +1,159 @@ +// Copyright 2020-2022 Litentry Technologies GmbH. +// This file is part of Litentry. +// +// Litentry is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Litentry is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Litentry. If not, see . + +use crate::{ + format, AuthorApi, Error, HandleState, Hash, SgxExternalitiesTrait, ShardIdentifier, + ShieldingCryptoDecrypt, ShieldingCryptoEncrypt, StfEnclaveSigning, StfTaskContext, +}; +use codec::Decode; +use ita_sgx_runtime::IdentityManagement; +use lc_stf_task_sender::{stf_task_sender, RequestType}; +use litentry_primitives::{Assertion, IdentityWebType, Web2Network}; +use log::*; + +// lifetime elision: StfTaskContext is guaranteed to outlive the fn +pub fn run_stf_task_receiver(context: &StfTaskContext) -> Result<(), Error> +where + K: ShieldingCryptoDecrypt + ShieldingCryptoEncrypt + Clone, + A: AuthorApi, + S: StfEnclaveSigning, + H: HandleState, + H::StateT: SgxExternalitiesTrait, +{ + let receiver = stf_task_sender::init_stf_task_sender_storage() + .map_err(|e| Error::OtherError(format!("read storage error:{:?}", e)))?; + + // TODO: When an error occurs, send the extrinsic (error message) to the parachain + // TODO: error handling still incomplete, we only print logs but no error handling + // TODO: we can further simplify the handling logic + loop { + let request_type = receiver + .recv() + .map_err(|e| Error::OtherError(format!("receiver error:{:?}", e)))?; + + match request_type { + RequestType::Web2IdentityVerification(request) => + match lc_identity_verification::web2::verify(request.clone()) { + Err(e) => { + error!("error verify web2: {:?}", e) + }, + Ok(_) => { + context.decode_and_submit_trusted_call( + request.encoded_shard, + request.encoded_callback, + )?; + }, + }, + RequestType::Web3IdentityVerification(request) => + match lc_identity_verification::web3::verify( + request.who.clone(), + request.identity.clone(), + request.challenge_code, + request.validation_data.clone(), + ) { + Err(e) => { + error!("error verify web3: {:?}", e) + }, + Ok(_) => { + context.decode_and_submit_trusted_call( + request.encoded_shard, + request.encoded_callback, + )?; + }, + }, + RequestType::AssertionVerification(request) => match request.assertion { + Assertion::A1 => { + if let Err(e) = lc_assertion_build::a1::build(request.vec_identity) { + error!("error verify assertion1: {:?}", e) + } + }, + Assertion::A2(guild_id, handler) => { + for identity in request.vec_identity { + if identity.web_type == IdentityWebType::Web2(Web2Network::Discord) { + if let Err(e) = + lc_assertion_build::a2::build(guild_id.clone(), handler.clone()) + { + error!("error verify assertion2: {:?}", e) + } else { + // When result is Ok, + break + } + } + } + }, + Assertion::A3(guild_id, handler) => { + for identity in request.vec_identity { + if identity.web_type == IdentityWebType::Web2(Web2Network::Discord) { + if let Err(e) = + lc_assertion_build::a3::build(guild_id.clone(), handler.clone()) + { + error!("error verify assertion3: {:?}", e) + } else { + // When result is Ok, + break + } + } + } + }, + Assertion::A5(twitter_account, original_tweet_id) => + match lc_assertion_build::a5::build( + request.vec_identity.to_vec(), + twitter_account, + original_tweet_id, + ) { + Ok(_) => {}, + Err(e) => { + log::error!("error verify assertion5: {:?}", e) + }, + }, + Assertion::A6 => match lc_assertion_build::a6::build(request.vec_identity.to_vec()) + { + Ok(_) => {}, + Err(e) => { + log::error!("error verify assertion6: {:?}", e) + }, + }, + _ => { + unimplemented!() + }, + }, + // only used for testing + // demonstrate how to read the storage in the stf-task handling with the loaded state + // in real cases we prefer to read the state ahead and sent the related storage as parameters in `Request` + RequestType::SetUserShieldingKey(request) => { + let shard = ShardIdentifier::decode(&mut request.encoded_shard.as_slice()) + .map_err(|e| { + Error::OtherError(format!("error decoding ShardIdentifier {:?}", e)) + })?; + + let mut state = context + .state_handler + .load(&shard) + .map_err(|e| Error::OtherError(format!("load state failed: {:?}", e)))?; + + let key = + state.execute_with(|| IdentityManagement::user_shielding_keys(&request.who)); + + debug!("in RequestType::SetUserShieldingKey read key is: {:?}", key); + + context.decode_and_submit_trusted_call( + request.encoded_shard, + request.encoded_callback, + )?; + }, + } + } +} diff --git a/tee-worker/litentry/core/stf-task/sender/Cargo.toml b/tee-worker/litentry/core/stf-task/sender/Cargo.toml new file mode 100644 index 0000000000..b843ea5f31 --- /dev/null +++ b/tee-worker/litentry/core/stf-task/sender/Cargo.toml @@ -0,0 +1,65 @@ +[package] +authors = ["Litentry dev"] +edition = "2021" +name = "lc-stf-task-sender" +version = "0.1.0" + +[dependencies] +# std dependencies +http = { version = "0.2", optional = true } +http_req = { optional = true, features = ["rust-tls"], branch = "master", git = "https://github.com/integritee-network/http_req" } +thiserror = { version = "1.0.26", optional = true } +url = { version = "2.0.0", optional = true } + +# sgx dependencies +http-sgx = { package = "http", git = "https://github.com/integritee-network/http-sgx.git", branch = "sgx-experimental", optional = true } +http_req-sgx = { package = "http_req", git = "https://github.com/integritee-network/http_req", default-features = false, features = ["rust-tls", "sgx"], optional = true } +sgx_tstd = { branch = "master", git = "https://github.com/apache/teaclave-sgx-sdk.git", features = ["net", "thread"], optional = true } +thiserror_sgx = { package = "thiserror", git = "https://github.com/mesalock-linux/thiserror-sgx", tag = "sgx_1.1.3", optional = true } +url_sgx = { package = "url", git = "https://github.com/mesalock-linux/rust-url-sgx", tag = "sgx_1.1.3", optional = true } + +# no_std dependencies +codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } +lazy_static = { version = "1.1.0", features = ["spin_no_std"] } +log = { version = "0.4", default-features = false } +serde = { version = "1.0", default-features = false, features = ["derive", "alloc"] } +serde_json = { version = "1.0", default-features = false, features = ["alloc"] } + +# substrate dependencies +sp-runtime = { git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.29", default-features = false } +sp-std = { git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.29", default-features = false } + +# internal dependencies +itc-rest-client = { path = "../../../../core/rest-client", default-features = false } +itp-ocall-api = { path = "../../../../core-primitives/ocall-api", default-features = false } +itp-sgx-crypto = { path = "../../../../core-primitives/sgx/crypto", default-features = false } +itp-types = { path = "../../../../core-primitives/types", default-features = false } +itp-utils = { path = "../../../../core-primitives/utils", default-features = false } + +# litentry +litentry-primitives = { path = "../../../primitives", default-features = false } + +[features] +default = ["std"] +sgx = [ + "http-sgx", + "http_req-sgx", + "itc-rest-client/sgx", + "sgx_tstd", + "thiserror_sgx", + "url_sgx", +] +std = [ + "http", + "http_req", + "itc-rest-client/std", + "log/std", + "serde/std", + "serde_json/std", + "sp-runtime/std", + "sp-std/std", + "thiserror", + "url", + "itp-types/std", + "itp-utils/std", +] diff --git a/tee-worker/litentry/core/stf-task/sender/src/error.rs b/tee-worker/litentry/core/stf-task/sender/src/error.rs new file mode 100644 index 0000000000..fd0f90f439 --- /dev/null +++ b/tee-worker/litentry/core/stf-task/sender/src/error.rs @@ -0,0 +1,38 @@ +// Copyright 2020-2022 Litentry Technologies GmbH. +// This file is part of Litentry. +// +// Litentry is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Litentry is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Litentry. If not, see . + +#[cfg(all(not(feature = "std"), feature = "sgx"))] +use crate::sgx_reexport_prelude::*; +use std::boxed::Box; + +pub type Result = core::result::Result; + +/// REST client error +#[derive(Debug, thiserror::Error)] +pub enum Error { + #[error("Daemon sender was not initialized")] + ComponentNotInitialized, + #[error("Could not access Mutex")] + MutexAccess, + #[error("URL parse error: {0}")] + Url(#[from] url::ParseError), + #[error(transparent)] + Other(#[from] Box), + #[error("failed create extrinsic")] + FailedCreateExtrinsic, + #[error("failed send extrinsic")] + FailedSendExtrinsic, +} diff --git a/tee-worker/litentry/core/stf-task/sender/src/lib.rs b/tee-worker/litentry/core/stf-task/sender/src/lib.rs new file mode 100644 index 0000000000..4e78e50a3f --- /dev/null +++ b/tee-worker/litentry/core/stf-task/sender/src/lib.rs @@ -0,0 +1,142 @@ +// Copyright 2020-2022 Litentry Technologies GmbH. +// This file is part of Litentry. +// +// Litentry is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Litentry is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Litentry. If not, see . + +#![feature(trait_alias)] +#![cfg_attr(not(feature = "std"), no_std)] + +#[cfg(all(feature = "std", feature = "sgx"))] +compile_error!("feature \"std\" and feature \"sgx\" cannot be enabled at the same time"); + +#[cfg(all(not(feature = "std"), feature = "sgx"))] +extern crate sgx_tstd as std; + +// re-export module to properly feature gate sgx and regular std environment +#[cfg(all(not(feature = "std"), feature = "sgx"))] +pub mod sgx_reexport_prelude { + pub use thiserror_sgx as thiserror; + pub use url_sgx as url; +} + +use itp_types::AccountId; +pub mod error; +pub mod stf_task_sender; +pub use error::Result; + +use sp_runtime::{traits::ConstU32, BoundedVec}; +use sp_std::vec::Vec; + +use codec::{Decode, Encode}; +use litentry_primitives::{ + Assertion, ChallengeCode, Identity, Web2ValidationData, Web3ValidationData, +}; + +/// Here a few Request structs are defined for asynchronously stf-tasks handling. +/// A `callback` exists for some request types to submit a callback TrustedCall to top pool. +/// We use the encoded version just to avoid cyclic dependency, otherwise we have +/// ita-stf -> lc-stf-task-sender -> ita-stf +/// +/// In this way we make sure the state is processed "chronologically" by the StfExecutor. +/// We can't write any state in this state, otherwise we can be trapped into a situation +/// where the state doesn't match the apriori state that is recorded before executing any +/// trusted calls in block production (InvalidAprioriHash error). +/// +/// Reading state is not a problem. However, we prefer to read the required storage before +/// sending the stf-task and pass it as parameters in `Request`, e.g. `challenge_code` below. +/// The reason is we actually want the "snapshot" state when the preflight TrustedCall gets +/// executed instead of the "live" state. +/// +/// The callback TrustedCall will be appended to the end of top pool but we don't see a +/// problem. In case some preflight TrustedCall and callback TrustedCall are going to change +/// the same storage, we should implement them carefully and always treat it as if both +/// TrustedCalls can get executed in any order. +/// +/// For more information, please see: +/// https://github.com/litentry/tee-worker/issues/110 +/// https://www.notion.so/web3builders/Sidechain-block-importer-and-block-production-28292233b4c74f4ab8110a0014f8d9df + +#[derive(Encode, Decode, Clone, Debug, PartialEq, Eq)] +pub struct Web2IdentityVerificationRequest { + pub encoded_shard: Vec, + pub who: AccountId, + pub identity: Identity, + pub challenge_code: ChallengeCode, + pub validation_data: Web2ValidationData, + pub bn: litentry_primitives::ParentchainBlockNumber, //Parentchain BlockNumber + pub encoded_callback: Vec, +} + +/// TODO: adapt Web3 struct fields later +#[derive(Encode, Decode, Clone, Debug, PartialEq, Eq)] +pub struct Web3IdentityVerificationRequest { + pub encoded_shard: Vec, + pub who: AccountId, + pub identity: Identity, + pub challenge_code: ChallengeCode, + pub validation_data: Web3ValidationData, + pub bn: litentry_primitives::ParentchainBlockNumber, //Parentchain BlockNumber + pub encoded_callback: Vec, +} + +pub type MaxIdentityLength = ConstU32<64>; +/// TODO: adapt struct fields later +#[derive(Encode, Decode, Clone, Debug, PartialEq, Eq)] +pub struct AssertionBuildRequest { + pub encoded_shard: Vec, + pub who: AccountId, + pub assertion: Assertion, + pub vec_identity: BoundedVec, +} + +#[derive(Encode, Decode, Clone, Debug, PartialEq, Eq)] +pub struct SetUserShieldingKeyRequest { + pub encoded_shard: Vec, + pub who: AccountId, + pub encoded_callback: Vec, +} + +pub enum RequestType { + Web2IdentityVerification(Web2IdentityVerificationRequest), + Web3IdentityVerification(Web3IdentityVerificationRequest), + AssertionVerification(AssertionBuildRequest), + // set the user shielding key async - just to showcase how to + // async process the request in stf-task-receiver + // In real scenario it should be done synchronously + SetUserShieldingKey(SetUserShieldingKeyRequest), +} + +impl From for RequestType { + fn from(r: Web2IdentityVerificationRequest) -> Self { + RequestType::Web2IdentityVerification(r) + } +} + +impl From for RequestType { + fn from(r: Web3IdentityVerificationRequest) -> Self { + RequestType::Web3IdentityVerification(r) + } +} + +impl From for RequestType { + fn from(r: AssertionBuildRequest) -> Self { + RequestType::AssertionVerification(r) + } +} + +impl From for RequestType { + fn from(r: SetUserShieldingKeyRequest) -> Self { + RequestType::SetUserShieldingKey(r) + } +} diff --git a/tee-worker/litentry/core/stf-task/sender/src/stf_task_sender.rs b/tee-worker/litentry/core/stf-task/sender/src/stf_task_sender.rs new file mode 100644 index 0000000000..c8dfe1418e --- /dev/null +++ b/tee-worker/litentry/core/stf-task/sender/src/stf_task_sender.rs @@ -0,0 +1,95 @@ +// Copyright 2020-2022 Litentry Technologies GmbH. +// This file is part of Litentry. +// +// Litentry is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Litentry is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Litentry. If not, see . +use crate::error::{Error, Result}; +use lazy_static::lazy_static; +use std::sync::{ + mpsc::{channel, Receiver, Sender}, + Arc, +}; + +#[cfg(feature = "sgx")] +use std::sync::SgxMutex as Mutex; + +use crate::RequestType; +#[cfg(feature = "std")] +use std::sync::Mutex; + +pub type StfSender = Sender; + +// Global storage of the sender. Should not be accessed directly. +lazy_static! { + static ref GLOBAL_STF_REQUEST_TASK: Arc>> = + Arc::new(Mutex::new(Default::default())); +} + +/// Trait to send an stf request to the stf request thread. +pub trait SendStfRequest { + fn send_stf_request(&self, request: RequestType) -> Result<()>; +} + +/// Struct to access the `send_stf_request` function. +pub struct StfRequestSender {} +impl StfRequestSender { + pub fn new() -> Self { + Self {} + } +} + +impl Default for StfRequestSender { + fn default() -> Self { + Self::new() + } +} + +impl SendStfRequest for StfRequestSender { + fn send_stf_request(&self, request: RequestType) -> Result<()> { + // Acquire lock on extrinsic sender + let mutex_guard = GLOBAL_STF_REQUEST_TASK.lock().map_err(|_| Error::MutexAccess)?; + + let stf_task_sender = mutex_guard.clone().ok_or(Error::ComponentNotInitialized)?; + + // Release mutex lock, so we don't block the lock longer than necessary. + drop(mutex_guard); + + // Send the request to the receiver loop. + stf_task_sender.send(request) + } +} + +/// Initialization of the extrinsic sender. Needs to be called before any sender access. +pub fn init_stf_task_sender_storage() -> Result> { + let (sender, receiver) = channel(); + let mut stf_task_storage = GLOBAL_STF_REQUEST_TASK.lock().map_err(|_| Error::MutexAccess)?; + *stf_task_storage = Some(StfTaskSender::new(sender)); + Ok(receiver) +} + +/// Wrapping struct around the actual sender. Should not be accessed directly. +#[derive(Clone, Debug)] +struct StfTaskSender { + sender: StfSender, +} + +impl StfTaskSender { + pub fn new(sender: StfSender) -> Self { + Self { sender } + } + + fn send(&self, request: RequestType) -> Result<()> { + self.sender.send(request).map_err(|e| Error::Other(e.into()))?; + Ok(()) + } +} diff --git a/tee-worker/litentry/pallets/account-linker/Cargo.toml b/tee-worker/litentry/pallets/account-linker/Cargo.toml new file mode 100644 index 0000000000..60f4bafc7f --- /dev/null +++ b/tee-worker/litentry/pallets/account-linker/Cargo.toml @@ -0,0 +1,47 @@ +[package] +authors = ['Litentry Dev'] +description = 'FRAME pallet template for defining custom runtime logic.' +edition = '2021' +homepage = 'https://litentry.com' +license = 'Unlicense' +name = 'pallet-sgx-account-linker' +repository = 'https://github.com/litentry/litentry-pallets' +version = '0.1.0' + +[package.metadata.docs.rs] +targets = ['x86_64-unknown-linux-gnu'] + +[dependencies] +log = { version = "0.4", default-features = false } +ripemd160 = { default-features = false, version = "0.9.1" } +scale-info = { version = "2.0", default-features = false, features = ["derive"] } +sha2 = { default-features = false, version = "0.9.5" } +# no_std +codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive", "chain-error"] } +frame-support = { default-features = false, git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.29" } +frame-system = { default-features = false, git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.29" } +sp-core = { default-features = false, git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.29" } +sp-io = { default-features = false, git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.29" } +sp-runtime = { default-features = false, git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.29" } +sp-std = { default-features = false, git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.29" } + +[dev-dependencies] +bitcoin = { version = "0.25.2", features = ["rand"] } +hex = "0.4.3" +parity-crypto = { version = "0.9.0", features = ["publickey"] } +serde = { version = "1.0" } + +[features] +default = ['std'] +std = [ + 'codec/std', + 'frame-support/std', + 'frame-system/std', + "sp-io/std", + "sp-core/std", + "sp-std/std", + "sha2/std", + "ripemd160/std", + "log/std", + "scale-info/std", +] diff --git a/tee-worker/litentry/pallets/account-linker/src/lib.rs b/tee-worker/litentry/pallets/account-linker/src/lib.rs new file mode 100644 index 0000000000..70989a9af4 --- /dev/null +++ b/tee-worker/litentry/pallets/account-linker/src/lib.rs @@ -0,0 +1,389 @@ +//! # AccountLinker Pallet +//! +//! The AccountLinker pallet provides functionality for linking a Litentry account to account at +//! other networks. (currently support Ethereum (BSC), BTC and Substrate based address) +//! +//! ## Overview +//! +//! The AccountLinker pallet stores the linking relation between Litentry accounts and accounts at other +//! networks. It also offers extrinscs for user to update the linking relation. For each linking relation, +//! user may choose to freshly link new account or replace an existing linked account with a new provided one. +//! +//! ## Interface +//! +//! ### Dispatchable Functions +//! +//! * `link_eth` - Link an Ethereum address to a Litentry account providing a proof signature +//! from the private key of that Ethereum address. +//! * `link_btc` - Link an BTC address to a Litentry account providing a proof signature +//! from the private key of that BTC address. +//! * `link_sub` - Initiate a link request to link a substrate based address to Litentry address +//! +//! [`Call`]: ./enum.Call.html +//! [`Config`]: ./trait.Config.html + +#![cfg_attr(not(feature = "std"), no_std)] + +#[cfg(test)] +mod mock; + +#[cfg(test)] +mod tests; + +pub use pallet::*; + +mod util_eth; +pub mod weights; + +type EthAddress = [u8; 20]; +// rsv signature +type Signature = [u8; 65]; + +#[frame_support::pallet] +pub mod pallet { + use crate::*; + use codec::Encode; + use frame_support::{dispatch::DispatchResultWithPostInfo, pallet_prelude::*}; + use frame_system::{ensure_signed, pallet_prelude::*}; + use sp_core::{ed25519, sr25519}; + use sp_std::prelude::*; + + use weights::WeightInfo; + pub const EXPIRING_BLOCK_NUMBER_MAX: u32 = 10 * 60 * 24 * 30; // 30 days for 6s per block + pub const MAX_ETH_LINKS: usize = 3; + pub const MAX_BTC_LINKS: usize = 3; + pub const MAX_SUB_LINKS: usize = 3; + + #[derive(Encode, Decode, Clone, Debug, Copy, Eq, PartialEq, TypeInfo)] + pub enum NetworkType { + Kusama, + Polkadot, + KusamaParachain(u32), + PolkadotParachain(u32), + } + + #[derive(Encode, Decode, Clone, Debug, Eq, PartialEq, TypeInfo)] + pub enum MultiSignature { + Sr25519Signature([u8; 64]), + Ed25519Signature([u8; 64]), + EcdsaSignature([u8; 65]), + } + + #[derive(Encode, Decode, Clone, Debug, Eq, PartialEq, TypeInfo)] + pub struct LinkedSubAccount { + network_type: NetworkType, + account_id: AccountId, + } + + #[pallet::config] + pub trait Config: frame_system::Config { + type Event: From> + IsType<::Event>; + type WeightInfo: WeightInfo; + } + + #[pallet::event] + #[pallet::generate_deposit(pub(super) fn deposit_event)] + pub enum Event { + /// Ethereum address successfully linked. \[Lintentry account, Ethereum account\] + EthAddressLinked(T::AccountId, Vec), + /// BTC address successfully linked. \[Lintentry account, BTC account\] + BtcAddressLinked(T::AccountId, Vec), + /// Substrate based address successfully linked. \[Lintentry account, substrate account\] + SubAddressLinked(T::AccountId, LinkedSubAccount), + } + + #[pallet::error] + pub enum Error { + // Cannot recover the signature + EcdsaRecoverFailure, + // Link request expired + LinkRequestExpired, + // Provided address mismatch the address recovered from signature recovery + UnexpectedAddress, + // Unexpected ethereum message length error + UnexpectedEthMsgLength, + // Invalid BTC address to link + InvalidBTCAddress, + // Expiration block number is too far away from now + InvalidExpiringBlockNumber, + // Can't get layer one block number + LayerOneBlockNumberNotAvailable, + // Signature is wrong + WrongSignature, + // Expected AccountId is [u8; 32] + UnexpectedAccountId, + } + + #[pallet::hooks] + impl Hooks> for Pallet {} + + #[pallet::pallet] + #[pallet::generate_store(pub(super) trait Store)] + #[pallet::without_storage_info] + pub struct Pallet(_); + + #[pallet::storage] + #[pallet::getter(fn eth_addresses)] + pub(super) type EthereumLink = + StorageMap<_, Blake2_128Concat, T::AccountId, Vec, ValueQuery>; + + #[pallet::storage] + #[pallet::getter(fn btc_addresses)] + pub(super) type BitcoinLink = + StorageMap<_, Blake2_128Concat, T::AccountId, Vec>, ValueQuery>; + + #[pallet::storage] + #[pallet::getter(fn sub_addresses)] + pub(super) type SubLink = StorageMap< + _, + Blake2_128Concat, + T::AccountId, + Vec>, + ValueQuery, + >; + + #[pallet::call] + impl Pallet { + /// Link an Ethereum address to a Litentry account providing a proof signature from the private key + /// of that Ethereum address. The extrinsic supposed to be executed in the sgx. + /// + /// The runtime needs to ensure that a malicious index can be handled correctly. + /// Currently, when vec.len > MAX_ETH_LINKS, replacement will always happen at the final index. + /// Otherwise it will use the next new slot unless index is valid against a currently available slot. + /// + /// Parameters: + /// - `account`: The Litentry address that is to be linked + /// - `index`: The index of the linked Ethereum address that the user wants to replace with. + /// - `addr_expected`: The intended Ethereum address to link to the origin's Litentry address + /// - `layer_one_block_number`: The current layer one block number + /// - `expiring_block_number`: The block number after which this link request will expire + /// - `sig`: The rsv-signature generated by the private key of the addr_expected + /// + /// Emits `EthAddressLinked` event when successful. + #[pallet::weight(T::WeightInfo::link_eth())] + pub fn link_eth( + origin: OriginFor, + account: T::AccountId, + index: u32, + addr_expected: EthAddress, + layer_one_block_number: T::BlockNumber, + expiring_block_number: T::BlockNumber, + sig: Signature, + ) -> DispatchResultWithPostInfo { + // in sgx runtime, the account who want to link ethereum address don't have the balance to + // submit extrinsic, the origin could be the root account + let _ = ensure_signed(origin)?; + Self::do_link_eth( + account, + index, + addr_expected, + expiring_block_number, + layer_one_block_number, + sig, + ) + } + + /// Link a substrate based address to a Litentry address + /// + /// The runtime needs to ensure that a malicious index can be handled correctly. + /// Currently, when vec.len > MAX_ETH_LINKS, replacement will always happen at the final index. + /// Otherwise it will use the next new slot unless index is valid against a currently available slot. + /// + /// Parameters: + /// - `account`: The Litentry address that is to be linked + /// + /// Emits `SubAddressLinked` event when successful. + // TODO will update weight when do the benchmark testing + + #[allow(clippy::too_many_arguments)] + #[pallet::weight(T::WeightInfo::link_eth())] + pub fn link_sub( + origin: OriginFor, + account: T::AccountId, + index: u32, + network_type: NetworkType, + linked_account: T::AccountId, + layer_one_block_number: T::BlockNumber, + expiring_block_number: T::BlockNumber, + sig: MultiSignature, + ) -> DispatchResultWithPostInfo { + // in sgx runtime, the account who want to link ethereum address don't have the balance to + // submit extrinsic, the origin could be the root account + let _ = ensure_signed(origin)?; + Self::do_link_sub( + account, + index, + network_type, + linked_account, + expiring_block_number, + layer_one_block_number, + sig, + ) + } + } + + impl Pallet { + /// Assemble the message that the user has signed + /// Format: "Link Litentry: " + Litentry account + expiring block number + fn generate_eth_raw_message( + account: &T::AccountId, + expiring_block_number: T::BlockNumber, + ) -> Vec { + let mut bytes = b"Link Litentry: ".encode(); + let mut account_vec = account.encode(); + let mut expiring_block_number_vec = expiring_block_number.encode(); + + bytes.append(&mut account_vec); + bytes.append(&mut expiring_block_number_vec); + bytes + } + + /// Assemble the message that the user has signed + /// Format: "Link Litentry: " + network_type + Litentry account + expiring block number + fn generate_sub_raw_message( + account: &T::AccountId, + network_type: NetworkType, + expiring_block_number: T::BlockNumber, + ) -> Vec { + let mut bytes = b"Link Litentry: ".encode(); + let mut network_type_vec = network_type.encode(); + let mut account_vec = account.encode(); + let mut expiring_block_number_vec = expiring_block_number.encode(); + + bytes.append(&mut network_type_vec); + bytes.append(&mut account_vec); + bytes.append(&mut expiring_block_number_vec); + bytes + } + + pub fn do_link_eth( + account: T::AccountId, + index: u32, + addr_expected: EthAddress, + expiring_block_number: T::BlockNumber, + layer_one_blocknumber: T::BlockNumber, + sig: Signature, + ) -> DispatchResultWithPostInfo { + ensure!(expiring_block_number > layer_one_blocknumber, Error::::LinkRequestExpired); + ensure!( + (expiring_block_number - layer_one_blocknumber) + < T::BlockNumber::from(EXPIRING_BLOCK_NUMBER_MAX), + Error::::InvalidExpiringBlockNumber + ); + + let bytes = Self::generate_eth_raw_message(&account, expiring_block_number); + + let hash = + util_eth::eth_data_hash(bytes).map_err(|_| Error::::UnexpectedEthMsgLength)?; + + let mut msg = [0u8; 32]; + msg[..32].copy_from_slice(&hash[..32]); + + let addr = + util_eth::addr_from_sig(msg, sig).map_err(|_| Error::::EcdsaRecoverFailure)?; + ensure!(addr == addr_expected, Error::::UnexpectedAddress); + + EthereumLink::::mutate(&account, |addrs| { + let index = index as usize; + // NOTE: allow linking `MAX_ETH_LINKS` eth addresses. + if (index >= addrs.len()) && (addrs.len() != MAX_ETH_LINKS) { + addrs.push(addr); + } else if (index >= addrs.len()) && (addrs.len() == MAX_ETH_LINKS) { + addrs[MAX_ETH_LINKS - 1] = addr; + } else { + addrs[index] = addr; + } + }); + + Self::deposit_event(Event::EthAddressLinked(account, addr.to_vec())); + + Ok(().into()) + } + + pub fn do_link_sub( + account: T::AccountId, + index: u32, + network_type: NetworkType, + linked_account: T::AccountId, + expiring_block_number: T::BlockNumber, + layer_one_blocknumber: T::BlockNumber, + multi_sig: MultiSignature, + ) -> DispatchResultWithPostInfo { + ensure!(expiring_block_number > layer_one_blocknumber, Error::::LinkRequestExpired); + ensure!( + (expiring_block_number - layer_one_blocknumber) + < T::BlockNumber::from(EXPIRING_BLOCK_NUMBER_MAX), + Error::::InvalidExpiringBlockNumber + ); + + let bytes = Self::generate_sub_raw_message( + &linked_account, + network_type, + expiring_block_number, + ); + + // get the public key + let account_vec = linked_account.encode(); + ensure!(account_vec.len() == 32, Error::::UnexpectedAccountId); + + let mut public_key = [0u8; 32]; + public_key[..32].copy_from_slice(&account_vec[..32]); + + // verify signature according to encryption type + match multi_sig { + MultiSignature::Sr25519Signature(sig) => { + let msg = sp_io::hashing::keccak_256(&bytes); + + ensure!( + sp_io::crypto::sr25519_verify( + &sr25519::Signature(sig), + &msg, + &sr25519::Public(public_key) + ), + Error::::WrongSignature + ); + }, + MultiSignature::Ed25519Signature(sig) => { + let msg = sp_io::hashing::keccak_256(&bytes); + + ensure!( + sp_io::crypto::ed25519_verify( + &ed25519::Signature(sig), + &msg, + &ed25519::Public(public_key) + ), + Error::::WrongSignature + ); + }, + MultiSignature::EcdsaSignature(sig) => { + let msg = sp_io::hashing::blake2_256(&bytes); + + let recovered_public_key = + sp_io::crypto::secp256k1_ecdsa_recover_compressed(&sig, &msg) + .map_err(|_| Error::::UnexpectedAddress)?; + let hashed_pk = sp_io::hashing::blake2_256(&recovered_public_key); + + ensure!(public_key == hashed_pk, Error::::WrongSignature); + }, + } + + let new_address = LinkedSubAccount { network_type, account_id: linked_account }; + + // insert new linked account into storage + SubLink::::mutate(&account, |addrs| { + let index = index as usize; + if (index >= addrs.len()) && (addrs.len() != MAX_SUB_LINKS) { + addrs.push(new_address.clone()); + } else if (index >= addrs.len()) && (addrs.len() == MAX_SUB_LINKS) { + addrs[MAX_SUB_LINKS - 1] = new_address.clone(); + } else { + addrs[index] = new_address.clone(); + } + }); + + Self::deposit_event(Event::SubAddressLinked(account, new_address)); + + Ok(().into()) + } + } +} diff --git a/tee-worker/litentry/pallets/account-linker/src/mock.rs b/tee-worker/litentry/pallets/account-linker/src/mock.rs new file mode 100644 index 0000000000..6b2972ceae --- /dev/null +++ b/tee-worker/litentry/pallets/account-linker/src/mock.rs @@ -0,0 +1,91 @@ +use crate as sgx_account_linker; +use frame_support::{ + parameter_types, + traits::{Everything, OnFinalize, OnInitialize}, +}; +use frame_system as system; +use sp_core::H256; +use sp_runtime::{ + generic, + traits::{BlakeTwo256, IdentityLookup}, + AccountId32, +}; + +pub use crate::MAX_ETH_LINKS; + +type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; +type Block = frame_system::mocking::MockBlock; + +// Configure a mock runtime to test the pallet. +frame_support::construct_runtime!( + pub enum Test where + Block = Block, + NodeBlock = Block, + UncheckedExtrinsic = UncheckedExtrinsic, + { + System: frame_system::{Pallet, Call, Config, Storage, Event}, + SgxAccountLinker: sgx_account_linker::{Pallet, Call, Storage, Event}, + } +); + +parameter_types! { + pub const BlockHashCount: u32 = 250; + pub const SS58Prefix: u8 = 42; +} + +impl system::Config for Test { + type BaseCallFilter = Everything; + type Origin = Origin; + type BlockWeights = (); + type BlockLength = (); + type DbWeight = (); + type Call = Call; + type Index = u32; + type BlockNumber = u32; + type Hash = H256; + type Hashing = BlakeTwo256; + type AccountId = AccountId32; + type Lookup = IdentityLookup; + type Header = generic::Header; + type Event = Event; + type BlockHashCount = BlockHashCount; + type Version = (); + type PalletInfo = PalletInfo; + type AccountData = (); + type OnNewAccount = (); + type OnKilledAccount = (); + type SystemWeightInfo = (); + type SS58Prefix = SS58Prefix; + type OnSetCode = (); + type MaxConsumers = frame_support::traits::ConstU32<16>; +} + +impl sgx_account_linker::Config for Test { + type Event = Event; + type WeightInfo = (); +} + +pub type SgxAccountLinkerError = sgx_account_linker::Error; + +// Build genesis storage according to the mock runtime. +pub fn new_test_ext() -> sp_io::TestExternalities { + system::GenesisConfig::default().build_storage::().unwrap().into() +} + +pub fn run_to_block(n: u32) { + while System::block_number() < n { + SgxAccountLinker::on_finalize(System::block_number()); + System::on_finalize(System::block_number()); + System::set_block_number(System::block_number() + 1); + System::on_initialize(System::block_number()); + SgxAccountLinker::on_initialize(System::block_number()); + } +} + +pub fn events() -> Vec { + let evt = System::events().into_iter().map(|evt| evt.event).collect::>(); + + System::reset_events(); + + evt +} diff --git a/tee-worker/litentry/pallets/account-linker/src/tests.rs b/tee-worker/litentry/pallets/account-linker/src/tests.rs new file mode 100644 index 0000000000..1006767a3f --- /dev/null +++ b/tee-worker/litentry/pallets/account-linker/src/tests.rs @@ -0,0 +1,436 @@ +use crate::mock::*; + +use codec::Encode; +use frame_support::{assert_noop, assert_ok}; +use parity_crypto::{ + publickey::{sign, Generator, KeyPair, Message, Random}, + Keccak256, +}; +use sp_runtime::AccountId32; + +use sp_core::{crypto::Pair, ecdsa, ed25519, sr25519}; + +fn generate_eth_raw_message(account: &AccountId32, block_number: u32) -> Message { + let mut bytes = b"\x19Ethereum Signed Message:\n51Link Litentry: ".encode(); + let mut account_vec = account.encode(); + let mut expiring_block_number_vec = block_number.encode(); + + bytes.append(&mut account_vec); + bytes.append(&mut expiring_block_number_vec); + + Message::from(bytes.keccak256()) +} + +fn generate_sub_raw_message( + account: &AccountId32, + network_type: crate::NetworkType, + expiring_block_number: u32, +) -> Vec { + let mut bytes = b"Link Litentry: ".encode(); + let mut network_type_vec = network_type.encode(); + let mut account_vec = account.encode(); + let mut expiring_block_number_vec = expiring_block_number.encode(); + + bytes.append(&mut network_type_vec); + bytes.append(&mut account_vec); + bytes.append(&mut expiring_block_number_vec); + bytes +} + +fn generate_sig(key_pair: &KeyPair, msg: &Message) -> [u8; 65] { + sign(key_pair.secret(), &msg).unwrap().into_electrum() +} + +fn generate_sr25519_sig(msg: [u8; 32]) -> sr25519::Signature { + // serect seed for Alice 0xe5be9a5092b81bca64be81d212e7f2f9eba183bb7a90954f7b76361f6edb5c0a + let alice_seed_str = "e5be9a5092b81bca64be81d212e7f2f9eba183bb7a90954f7b76361f6edb5c0a"; + let decoded_seed = hex::decode(alice_seed_str).unwrap(); + let mut alice_seed = [0_u8; 32]; + alice_seed[0..32].copy_from_slice(&decoded_seed[0..32]); + let pair = sr25519::Pair::from_seed(&alice_seed); + pair.sign(&msg) +} + +fn generate_ed25519_sig(msg: [u8; 32]) -> ed25519::Signature { + // bash-5.0$ target/release/subkey inspect //Alice --scheme Ed25519 + // Secret Key URI `//Alice` is account: + // Secret seed: 0xabf8e5bdbe30c65656c0a3cbd181ff8a56294a69dfedd27982aace4a76909115 + // Public key (hex): 0x88dc3417d5058ec4b4503e0c12ea1a0a89be200fe98922423d4334014fa6b0ee + // Account ID: 0x88dc3417d5058ec4b4503e0c12ea1a0a89be200fe98922423d4334014fa6b0ee + // Public key (SS58): 5FA9nQDVg267DEd8m1ZypXLBnvN7SFxYwV7ndqSYGiN9TTpu + // SS58 Address: 5FA9nQDVg267DEd8m1ZypXLBnvN7SFxYwV7ndqSYGiN9TTpu + let alice_seed_str = "abf8e5bdbe30c65656c0a3cbd181ff8a56294a69dfedd27982aace4a76909115"; + let decoded_seed = hex::decode(alice_seed_str).unwrap(); + let mut alice_seed = [0_u8; 32]; + alice_seed[0..32].copy_from_slice(&decoded_seed[0..32]); + let pair = ed25519::Pair::from_seed(&alice_seed); + pair.sign(&msg) +} + +fn generate_ecdsa_sig(msg: &[u8]) -> ecdsa::Signature { + // bash-5.0$ target/release/subkey inspect //Alice --scheme Ecdsa + // Secret Key URI `//Alice` is account: + // Secret seed: 0xcb6df9de1efca7a3998a8ead4e02159d5fa99c3e0d4fd6432667390bb4726854 + // Public key (hex): 0x020a1091341fe5664bfa1782d5e04779689068c916b04cb365ec3153755684d9a1 + // Account ID: 0x01e552298e47454041ea31273b4b630c64c104e4514aa3643490b8aaca9cf8ed + // Public key (SS58): KW39r9CJjAVzmkf9zQ4YDb2hqfAVGdRqn53eRqyruqpxAP5YL + // SS58 Address: 5C7C2Z5sWbytvHpuLTvzKunnnRwQxft1jiqrLD5rhucQ5S9X + let alice_seed_str = "cb6df9de1efca7a3998a8ead4e02159d5fa99c3e0d4fd6432667390bb4726854"; + let decoded_seed = hex::decode(alice_seed_str).unwrap(); + let mut alice_seed = [0_u8; 32]; + alice_seed[0..32].copy_from_slice(&decoded_seed[0..32]); + let pair = ecdsa::Pair::from_seed(&alice_seed); + pair.sign(&msg) +} + +#[test] +fn test_expired_block_number_eth() { + new_test_ext().execute_with(|| { + let account: AccountId32 = AccountId32::from([0u8; 32]); + let block_number: u32 = 100; + let layer_one_blocknumber: u32 = 1000; + + let mut gen = Random {}; + let key_pair = gen.generate(); + + let msg = generate_eth_raw_message(&account, block_number); + let sig = generate_sig(&key_pair, &msg); + + assert_noop!( + SgxAccountLinker::do_link_eth( + account.clone(), + 0, + key_pair.address().to_fixed_bytes(), + block_number, + layer_one_blocknumber, + sig + ), + SgxAccountLinkerError::LinkRequestExpired + ); + }); +} + +#[test] +fn test_invalid_expiring_block_number_eth() { + new_test_ext().execute_with(|| { + let account: AccountId32 = AccountId32::from([0u8; 32]); + let block_number: u32 = crate::EXPIRING_BLOCK_NUMBER_MAX + 10; + let layer_one_blocknumber: u32 = 1; + + let mut gen = Random {}; + let key_pair = gen.generate(); + + let msg = generate_eth_raw_message(&account, block_number); + let sig = generate_sig(&key_pair, &msg); + + assert_noop!( + SgxAccountLinker::do_link_eth( + account.clone(), + 0, + key_pair.address().to_fixed_bytes(), + block_number, + layer_one_blocknumber, + sig + ), + SgxAccountLinkerError::InvalidExpiringBlockNumber + ); + }); +} + +#[test] +fn test_unexpected_address_eth() { + new_test_ext().execute_with(|| { + let account: AccountId32 = AccountId32::from([72u8; 32]); + let block_number: u32 = 99999; + let layer_one_blocknumber: u32 = 10; + + let mut gen = Random {}; + let key_pair = gen.generate(); + + let msg = generate_eth_raw_message(&account, block_number); + let sig = generate_sig(&key_pair, &msg); + + assert_noop!( + SgxAccountLinker::do_link_eth( + account.clone(), + 0, + gen.generate().address().to_fixed_bytes(), + block_number, + layer_one_blocknumber, + sig + ), + SgxAccountLinkerError::UnexpectedAddress + ); + }); +} + +#[test] +fn test_insert_eth_address() { + new_test_ext().execute_with(|| { + run_to_block(1); + + let account: AccountId32 = AccountId32::from([5u8; 32]); + let block_number: u32 = 99999; + let layer_one_blocknumber: u32 = 10; + + let mut gen = Random {}; + let mut expected_vec = Vec::new(); + + for i in 0..(MAX_ETH_LINKS) { + let key_pair = gen.generate(); + + let msg = generate_eth_raw_message(&account, block_number + i as u32); + let sig = generate_sig(&key_pair, &msg); + + assert_ok!(SgxAccountLinker::do_link_eth( + account.clone(), + i as u32, + key_pair.address().to_fixed_bytes(), + block_number + i as u32, + layer_one_blocknumber, + sig + )); + + assert_eq!(SgxAccountLinker::eth_addresses(&account).len(), i + 1); + expected_vec.push(key_pair.address().to_fixed_bytes()); + assert_eq!( + events(), + [Event::SgxAccountLinker(crate::Event::EthAddressLinked( + account.clone(), + key_pair.address().to_fixed_bytes().to_vec() + )),] + ); + } + assert_eq!(SgxAccountLinker::eth_addresses(&account), expected_vec); + }); +} + +#[test] +fn test_update_eth_address() { + new_test_ext().execute_with(|| { + let account: AccountId32 = AccountId32::from([40u8; 32]); + let block_number: u32 = 99999; + let layer_one_blocknumber: u32 = 10; + + let mut gen = Random {}; + for i in 0..(MAX_ETH_LINKS) { + let key_pair = gen.generate(); + let msg = generate_eth_raw_message(&account, block_number + i as u32); + let sig = generate_sig(&key_pair, &msg); + + assert_ok!(SgxAccountLinker::do_link_eth( + account.clone(), + i as u32, + key_pair.address().to_fixed_bytes(), + block_number + i as u32, + layer_one_blocknumber, + sig + )); + } + + let index: u32 = 2 as u32; + // Retrieve previous addr + let addr_before_update = SgxAccountLinker::eth_addresses(&account)[index as usize]; + // Update addr at slot `index` + let key_pair = gen.generate(); + let block_number = block_number + 9 as u32; + let msg = generate_eth_raw_message(&account, block_number); + let sig = generate_sig(&key_pair, &msg); + + assert_ok!(SgxAccountLinker::do_link_eth( + account.clone(), + index, + key_pair.address().to_fixed_bytes(), + block_number, + layer_one_blocknumber, + sig + )); + + let updated_addr = SgxAccountLinker::eth_addresses(&account)[index as usize]; + assert_ne!(updated_addr, addr_before_update); + assert_eq!(updated_addr, key_pair.address().to_fixed_bytes()); + }); +} + +#[test] +fn test_eth_address_pool_overflow() { + new_test_ext().execute_with(|| { + let account: AccountId32 = AccountId32::from([113u8; 32]); + let block_number: u32 = 99999; + let layer_one_blocknumber: u32 = 10; + + let mut gen = Random {}; + let mut expected_vec = Vec::new(); + + for index in 0..(MAX_ETH_LINKS * 2) { + let key_pair = gen.generate(); + + let msg = generate_eth_raw_message(&account, block_number); + let sig = generate_sig(&key_pair, &msg); + + assert_ok!(SgxAccountLinker::do_link_eth( + account.clone(), + index as u32, + key_pair.address().to_fixed_bytes(), + block_number, + layer_one_blocknumber, + sig + )); + + if index < MAX_ETH_LINKS { + expected_vec.push(key_pair.address().to_fixed_bytes()); + } else { + expected_vec[MAX_ETH_LINKS - 1] = key_pair.address().to_fixed_bytes(); + } + } + assert_eq!(SgxAccountLinker::eth_addresses(&account).len(), MAX_ETH_LINKS); + assert_eq!(SgxAccountLinker::eth_addresses(&account), expected_vec); + }); +} + +#[test] +fn test_insert_fix_data() { + new_test_ext().execute_with(|| { + + run_to_block(1); + + // account id of Alice 0xd43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d + let account: AccountId32 = AccountId32::from([ + 0xd4, 0x35, 0x93, 0xc7, 0x15, 0xfd, 0xd3, 0x1c, 0x61, 0x14, 0x1a, 0xbd, 0x04, 0xa9, + 0x9f, 0xd6, 0x82, 0x2c, 0x85, 0x58, 0x85, 0x4c, 0xcd, 0xe3, 0x9a, 0x56, 0x84, 0xe7, + 0xa5, 0x6d, 0xa2, 0x7d, + ]); + + let block_number: u32 = 10000; + let layer_one_blocknumber: u32 = 10; + + let index = 0; + let eth_address_str = "4d88dc5d528a33e4b8be579e9476715f60060582"; + let decoded_address = hex::decode(eth_address_str).unwrap(); + let mut eth_address = [0_u8; 20]; + eth_address[0..20].copy_from_slice(&decoded_address[0..20]); + let signature_str = "318400f0f9bd15f0d8842870b510e996dffc944b77111ded03a4255c66e82d427132e765d5e6bb21ba046dbb98e28bb28cb2bebe0c8aced2c547aca60a5548921c"; + let decoded_signature = hex::decode(signature_str).unwrap(); + let mut signature = [0_u8; 65]; + signature[0..65].copy_from_slice(&decoded_signature[0..65]); + + assert_ok!(SgxAccountLinker::do_link_eth( + account.clone(), + index, + eth_address, + block_number, + layer_one_blocknumber, + signature + )); + }); +} + +#[test] +fn test_link_sub_sr25519_address() { + new_test_ext().execute_with(|| { + run_to_block(1); + + // account id of Alice 0xd43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d + let account: AccountId32 = AccountId32::from([ + 0xd4, 0x35, 0x93, 0xc7, 0x15, 0xfd, 0xd3, 0x1c, 0x61, 0x14, 0x1a, 0xbd, 0x04, 0xa9, + 0x9f, 0xd6, 0x82, 0x2c, 0x85, 0x58, 0x85, 0x4c, 0xcd, 0xe3, 0x9a, 0x56, 0x84, 0xe7, + 0xa5, 0x6d, 0xa2, 0x7d, + ]); + + let block_number: u32 = 10000; + let layer_one_blocknumber: u32 = 10; + + let index = 0_u32; + let network_type = crate::NetworkType::Kusama; + + let bytes = generate_sub_raw_message(&account.clone(), network_type, block_number); + let msg = sp_io::hashing::keccak_256(&bytes); + let signature_raw = generate_sr25519_sig(msg); + + //signature is 4cdfda29585e33fe1969ee67bab9d29e440973b8cfde47346043c91d6d763c317659686089c2642e9a64fa28a00b1767fd134484fc0146de6d9eefdf366d2c81 + let signature = crate::MultiSignature::Sr25519Signature(signature_raw.0); + + assert_ok!(SgxAccountLinker::do_link_sub( + account.clone(), + index, + network_type, + account.clone(), + block_number, + layer_one_blocknumber, + signature + )); + }); +} + +#[test] +fn test_link_sub_ed25519_address() { + new_test_ext().execute_with(|| { + run_to_block(1); + + // account id of Alice 0x88dc3417d5058ec4b4503e0c12ea1a0a89be200fe98922423d4334014fa6b0ee + let account: AccountId32 = AccountId32::from([ + 136, 220, 52, 23, 213, 5, 142, 196, 180, 80, 62, 12, 18, 234, 26, 10, 137, 190, 32, 15, + 233, 137, 34, 66, 61, 67, 52, 1, 79, 166, 176, 238, + ]); + + let block_number: u32 = 10000; + let layer_one_blocknumber: u32 = 10; + + let index = 0_u32; + let network_type = crate::NetworkType::Kusama; + + let bytes = generate_sub_raw_message(&account.clone(), network_type, block_number); + let msg = sp_io::hashing::keccak_256(&bytes); + + // signature is 0x08b1284cdaf008c80740d52be923cf24d45f8ba6e009bfd61a0a364c23df98c268dff3a8baabb47c8011ba39f76729aeab3c0281bfa45ef9389162fd78c18b08 + let signature_raw = generate_ed25519_sig(msg); + + let signature = crate::MultiSignature::Ed25519Signature(signature_raw.0); + + assert_ok!(SgxAccountLinker::do_link_sub( + account.clone(), + index, + network_type, + account.clone(), + block_number, + layer_one_blocknumber, + signature + )); + }); +} + +#[test] +fn test_link_sub_ecdsa_address() { + new_test_ext().execute_with(|| { + run_to_block(1); + + // account id of Alice 0x01e552298e47454041ea31273b4b630c64c104e4514aa3643490b8aaca9cf8ed + let account: AccountId32 = AccountId32::from([ + 1, 229, 82, 41, 142, 71, 69, 64, 65, 234, 49, 39, 59, 75, 99, 12, 100, 193, 4, 228, 81, + 74, 163, 100, 52, 144, 184, 170, 202, 156, 248, 237, + ]); + + let block_number: u32 = 10000; + let layer_one_blocknumber: u32 = 10; + + let index = 0_u32; + let network_type = crate::NetworkType::KusamaParachain(1); + + let bytes = generate_sub_raw_message(&account.clone(), network_type, block_number); + + // signature is 0xbf9484a706e5fadbd9ae8fd2e61f58c8aea387816903ef5b549af19cbf8c4fd831782058ea8f7acddc0e024f4d1ca155052fb04ad4115eeed75fefd6a7a6764301 + let signature_raw = generate_ecdsa_sig(&bytes[..]); + + let signature = crate::MultiSignature::EcdsaSignature(signature_raw.0); + + assert_ok!(SgxAccountLinker::do_link_sub( + account.clone(), + index, + network_type, + account.clone(), + block_number, + layer_one_blocknumber, + signature + )); + }); +} diff --git a/tee-worker/litentry/pallets/account-linker/src/util_eth.rs b/tee-worker/litentry/pallets/account-linker/src/util_eth.rs new file mode 100644 index 0000000000..a5929062e1 --- /dev/null +++ b/tee-worker/litentry/pallets/account-linker/src/util_eth.rs @@ -0,0 +1,153 @@ +use codec::Encode; +use sp_std::prelude::*; + +pub fn addr_from_sig(msg: [u8; 32], sig: [u8; 65]) -> Result<[u8; 20], sp_io::EcdsaVerifyError> { + let pubkey = sp_io::crypto::secp256k1_ecdsa_recover(&sig, &msg)?; + let hashed_pk = sp_io::hashing::keccak_256(&pubkey); + + let mut addr = [0u8; 20]; + addr[..20].copy_from_slice(&hashed_pk[12..32]); + Ok(addr) +} + +/// Returns a eth_sign-compatible hash of data to sign. +/// The data is prefixed with special message to prevent +/// malicious DApps from using the function to sign forged transactions. +pub fn eth_data_hash(mut data: Vec) -> Result<[u8; 32], &'static str> { + const MSG_LEN: usize = 51; + if data.len() != MSG_LEN { + log::error!( + "Ethereum message has an unexpected length {} !!! Expected is {}.", + data.len(), + MSG_LEN + ); + return Err("Unexpected ethereum message length!") + } + let mut length_bytes = usize_to_u8_array(data.len())?; + let mut eth_data = b"\x19Ethereum Signed Message:\n".encode(); + eth_data.append(&mut length_bytes); + eth_data.append(&mut data); + Ok(sp_io::hashing::keccak_256(ð_data)) +} + +/// Convert a usize type to a u8 array. +/// The input is first converted as a string with decimal presentation, +/// and then this string is converted to a byte array with UTF8 encoding. +/// To avoid unnecessary complexity, the current function supports up to +/// 2 digits unsigned decimal (range 0 - 99) +fn usize_to_u8_array(length: usize) -> Result, &'static str> { + if length >= 100 { + Err("Unexpected ethereum message length!") + } else { + let digits = b"0123456789".encode(); + let tens = length / 10; + let ones = length % 10; + + let mut vec_res: Vec = Vec::new(); + if tens != 0 { + vec_res.push(digits[tens]); + } + vec_res.push(digits[ones]); + Ok(vec_res) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use hex::decode; + + // A test helper function to add ethereum prefix before message hashing + pub fn eth_data_hash_test_helper(mut data: Vec) -> [u8; 32] { + let mut message_data = format!("\x19Ethereum Signed Message:\n{}", data.len()).into_bytes(); + message_data.append(&mut data); + sp_io::hashing::keccak_256(&message_data) + } + + #[test] + fn correct_recover() { + let msg = decode("61626364656667").unwrap(); + let msg = eth_data_hash_test_helper(msg); + + let sig_bytes = decode("5900a81f236e27be7ee2c796e0de9b383aadcd8b3c53fd881dd378f4c2bc1a54406be632a464c197131c668432f32a966a19354920686a8f8fdd9c9ab0a0dd011b").unwrap(); + let mut sig = [0u8; 65]; + sig[0..65].copy_from_slice(&sig_bytes[0..65]); + + let addr_expected_bytes = decode("Fe7cef4F3A7eF57Ac2401122fB51590bfDf9350a").unwrap(); + let mut addr_expected = [0u8; 20]; + addr_expected[0..20].copy_from_slice(&addr_expected_bytes[0..20]); + + let addr = addr_from_sig(msg, sig).ok().unwrap(); + assert_eq!(addr, addr_expected); + } + + #[test] + fn wrong_msg() { + let msg = decode("626364656667").unwrap(); + let msg = eth_data_hash_test_helper(msg); + + let sig_bytes = decode("5900a81f236e27be7ee2c796e0de9b383aadcd8b3c53fd881dd378f4c2bc1a54406be632a464c197131c668432f32a966a19354920686a8f8fdd9c9ab0a0dd011b").unwrap(); + let mut sig = [0u8; 65]; + sig[0..65].copy_from_slice(&sig_bytes[0..65]); + + let addr_expected_bytes = decode("Fe7cef4F3A7eF57Ac2401122fB51590bfDf9350a").unwrap(); + let mut addr_expected = [0u8; 20]; + addr_expected[0..20].copy_from_slice(&addr_expected_bytes[0..20]); + + let addr = addr_from_sig(msg, sig).ok().unwrap(); + assert_ne!(addr, addr_expected); + } + + #[test] + fn sig_from_another_addr() { + let msg = decode("61626364656667").unwrap(); + let msg = eth_data_hash_test_helper(msg); + + let sig_bytes = decode("a4543cd17d07a9b5207bbf4ccf3c9d47e0a292a6ce461427ebc50de24387887b14584651c3bc11376ba9fe662df325ced20f5c30dd782b6bee15cb474c206a341b").unwrap(); + let mut sig = [0u8; 65]; + sig[0..65].copy_from_slice(&sig_bytes[0..65]); + + let addr_expected_bytes = decode("Fe7cef4F3A7eF57Ac2401122fB51590bfDf9350a").unwrap(); + let mut addr_expected = [0u8; 20]; + addr_expected[0..20].copy_from_slice(&addr_expected_bytes[0..20]); + + let addr = addr_from_sig(msg, sig).ok().unwrap(); + assert_ne!(addr, addr_expected); + } + + #[test] + fn msg_with_unexpected_length() { + let msg = b"Link Litentry: 0123456789abcdef0123456789abcdef999".encode(); + assert_eq!(Err("Unexpected ethereum message length!"), eth_data_hash(msg)); + } + + #[test] + fn msg_with_expected_length() { + let msg = b"Link Litentry: 0123456789abcdef0123456789abcdef9999".encode(); + let res = eth_data_hash(msg.clone()).ok().unwrap(); + assert_eq!(eth_data_hash_test_helper(msg), res); + } + + // Test input with more than 2 digits + #[test] + fn usize_to_u8_array_input_too_large() { + let len: usize = 105; + assert_eq!(Err("Unexpected ethereum message length!"), usize_to_u8_array(len)) + } + + // Test inputs with one and two digits respectively + // UTF8 Table: + // 4 - 0x34 - 52 + // 0 - 0x30 - 48 + #[test] + fn usize_to_u8_array_input_one_digit() { + let len: usize = 4; + assert_eq!(Ok(vec![52]), usize_to_u8_array(len)) + } + + #[test] + fn usize_to_u8_array_input_two_digits() { + let len: usize = 40; + assert_eq!(Ok(vec![52, 48]), usize_to_u8_array(len)) + } +} diff --git a/tee-worker/litentry/pallets/account-linker/src/weights.rs b/tee-worker/litentry/pallets/account-linker/src/weights.rs new file mode 100644 index 0000000000..3f4ea0a6e7 --- /dev/null +++ b/tee-worker/litentry/pallets/account-linker/src/weights.rs @@ -0,0 +1,89 @@ +// This file is part of Substrate. + +// Copyright (C) 2021 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Autogenerated weights for pallet_account_linker +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 3.0.0 +//! DATE: 2021-02-25, STEPS: [20, ], REPEAT: 50, LOW RANGE: [], HIGH RANGE: [] +//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 128 + +// Executed Command: +// target/release/litentry-node +// benchmark +// --chain=dev +// --execution=wasm +// --wasm-execution=compiled +// --pallet=pallet_account_linker +// --extrinsic=* +// --heap-pages=4096 +// --steps=20 +// --repeat=50 +// --output=./pallets/account-linker/src/weights.rs +// --template=./.maintain/frame-weight-template.hbs + +#![cfg_attr(rustfmt, rustfmt_skip)] +#![allow(unused_parens)] +#![allow(unused_imports)] +#![allow(clippy::unnecessary_cast)] + +use frame_support::{ + traits::Get, + weights::{constants::RocksDbWeight, Weight}, +}; +use sp_std::marker::PhantomData; + +/// Weight functions needed for pallet_account_linker. +pub trait WeightInfo { + fn link_eth() -> Weight; + fn link_btc() -> Weight; + fn link_sub() -> Weight; +} + +/// Weights for pallet_account_linker using the Substrate node and recommended hardware. +pub struct SubstrateWeight(PhantomData); +impl WeightInfo for SubstrateWeight { + fn link_eth() -> Weight { + (324_000_000 as Weight) + .saturating_add(T::DbWeight::get().reads(1 as Weight)) + .saturating_add(T::DbWeight::get().writes(1 as Weight)) + } + fn link_btc() -> Weight { + (335_000_000 as Weight) + .saturating_add(T::DbWeight::get().reads(1 as Weight)) + .saturating_add(T::DbWeight::get().writes(1 as Weight)) + } + fn link_sub() -> Weight { + (335_000_000 as Weight).saturating_add(T::DbWeight::get().reads(1 as Weight)) + } +} + +// For backwards compatibility and tests +impl WeightInfo for () { + fn link_eth() -> Weight { + (324_000_000 as Weight) + .saturating_add(RocksDbWeight::get().reads(1 as Weight)) + .saturating_add(RocksDbWeight::get().writes(1 as Weight)) + } + fn link_btc() -> Weight { + (335_000_000 as Weight) + .saturating_add(RocksDbWeight::get().reads(1 as Weight)) + .saturating_add(RocksDbWeight::get().writes(1 as Weight)) + } + fn link_sub() -> Weight { + (335_000_000 as Weight).saturating_add(RocksDbWeight::get().reads(1 as Weight)) + } +} diff --git a/tee-worker/litentry/pallets/identity-management/Cargo.toml b/tee-worker/litentry/pallets/identity-management/Cargo.toml new file mode 100644 index 0000000000..4f883893ed --- /dev/null +++ b/tee-worker/litentry/pallets/identity-management/Cargo.toml @@ -0,0 +1,50 @@ +[package] +authors = ['Litentry Dev'] +edition = '2021' +homepage = 'https://litentry.com' +name = 'pallet-identity-management' +repository = 'https://github.com/litentry/litentry-parachain' +version = '0.1.0' + +[dependencies] +codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive", "max-encoded-len"] } +log = { version = "0.4", default-features = false } +scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } + +sp-core = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.29", default-features = false } +sp-io = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.29", default-features = false } +sp-runtime = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.29", default-features = false } +sp-std = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.29", default-features = false } + +frame-benchmarking = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.29", default-features = false, optional = true } +frame-support = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.29", default-features = false } +frame-system = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.29", default-features = false } +hex = { version = "0.4", default-features = false } + +serde = { version = "1.0", default-features = false, features = ["derive", "alloc"] } +serde_derive = { version = "1.0", default-features = false } +serde_json = { version = "1.0", default-features = false, features = ["alloc"] } + +litentry-primitives = { path = "../../primitives", default-features = false } + +[dev-dependencies] +pallet-balances = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.29", default-features = false } + +[features] +default = ["std"] + +std = [ + "codec/std", + "sp-std/std", + "sp-runtime/std", + "sp-io/std", + "sp-core/std", + "frame-support/std", + "frame-system/std", + "frame-benchmarking/std", + "log/std", + "serde/std", + "serde_json/std", + "pallet-balances/std", + "litentry-primitives/std", +] diff --git a/tee-worker/litentry/pallets/identity-management/src/identity_context.rs b/tee-worker/litentry/pallets/identity-management/src/identity_context.rs new file mode 100644 index 0000000000..96ac0e3f46 --- /dev/null +++ b/tee-worker/litentry/pallets/identity-management/src/identity_context.rs @@ -0,0 +1,64 @@ +// Copyright 2020-2022 Litentry Technologies GmbH. +// This file is part of Litentry. +// +// Litentry is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Litentry is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Litentry. If not, see . + +use codec::{Decode, Encode, MaxEncodedLen}; +use scale_info::TypeInfo; + +use crate::{Config, MetadataOf}; +use litentry_primitives::ParentchainBlockNumber; + +// The context associated with the (litentry-account, did) pair +// TODO: maybe we have better naming +#[derive(Clone, Eq, PartialEq, Debug, Encode, Decode, TypeInfo, MaxEncodedLen)] +#[scale_info(skip_type_params(T))] +#[codec(mel_bound())] +pub struct IdentityContext { + // the metadata + pub metadata: Option>, + // the block number (of parent chain) where the linking was intially requested + pub linking_request_block: Option, + // the block number (of parent chain) where the verification was intially requested + pub verification_request_block: Option, + // if this did is verified + pub is_verified: bool, +} + +// rust imposes overly restrictive bounds sometimes, see +// https://github.com/rust-lang/rust/issues/99463 +impl Default for IdentityContext { + fn default() -> Self { + Self { + metadata: None, + linking_request_block: None, + verification_request_block: None, + is_verified: false, + } + } +} + +impl IdentityContext { + pub fn new( + linking_request_block: ParentchainBlockNumber, + verification_request_block: ParentchainBlockNumber, + ) -> Self { + Self { + metadata: None, + linking_request_block: Some(linking_request_block), + verification_request_block: Some(verification_request_block), + is_verified: false, + } + } +} diff --git a/tee-worker/litentry/pallets/identity-management/src/lib.rs b/tee-worker/litentry/pallets/identity-management/src/lib.rs new file mode 100644 index 0000000000..1b0110c8b1 --- /dev/null +++ b/tee-worker/litentry/pallets/identity-management/src/lib.rs @@ -0,0 +1,257 @@ +// Copyright 2020-2022 Litentry Technologies GmbH. +// This file is part of Litentry. +// +// Litentry is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Litentry is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Litentry. If not, see . + +// Identity management pallet run within TEE(enclave) -- aka IMT +// The pallet is integrated in SGX-runtime, the extrinsics are supposed +// to be called only by enclave +// +// TODO: +// - origin management, only allow calls from TEE (= origin is signed with the ECC key), or root? +// otherwise we'd always require the origin has some fund +// - maybe don't emit events at all, or at least remove sensistive data +// - benchmarking + +#![cfg_attr(not(feature = "std"), no_std)] + +#[cfg(test)] +mod mock; + +#[cfg(test)] +mod tests; + +pub use pallet::*; +pub mod identity_context; + +use frame_support::{pallet_prelude::*, traits::StorageVersion}; +use frame_system::pallet_prelude::*; +pub use identity_context::IdentityContext; +pub use litentry_primitives::{ + ChallengeCode, Identity, ParentchainBlockNumber, UserShieldingKeyType, +}; +pub type BlockNumberOf = ::BlockNumber; +pub type MetadataOf = BoundedVec::MaxMetadataLength>; + +use sp_std::vec::Vec; + +#[frame_support::pallet] +pub mod pallet { + + use super::*; + + const STORAGE_VERSION: StorageVersion = StorageVersion::new(0); + + #[pallet::pallet] + #[pallet::generate_store(pub(super) trait Store)] + #[pallet::storage_version(STORAGE_VERSION)] + pub struct Pallet(_); + + #[pallet::config] + pub trait Config: frame_system::Config { + /// the event + type Event: From> + IsType<::Event>; + /// the manager origin for extrincis + type ManageOrigin: EnsureOrigin; + /// maximum metadata length + #[pallet::constant] + type MaxMetadataLength: Get; + /// maximum delay in block numbers between linking an identity and verifying an identity + #[pallet::constant] + type MaxVerificationDelay: Get; + } + + #[pallet::event] + #[pallet::generate_deposit(pub(super) fn deposit_event)] + pub enum Event { + /// user shielding key was set + UserShieldingKeySet { who: T::AccountId, key: UserShieldingKeyType }, + /// challenge code was set + ChallengeCodeSet { who: T::AccountId, identity: Identity, code: ChallengeCode }, + /// challenge code was removed + ChallengeCodeRemoved { who: T::AccountId, identity: Identity }, + /// an identity was linked + IdentityLinked { who: T::AccountId, identity: Identity }, + /// an identity was removed + IdentityUnlinked { who: T::AccountId, identity: Identity }, + } + + #[pallet::error] + pub enum Error { + /// challenge code doesn't exist + ChallengeCodeNotExist, + /// the pair (litentry-account, identity) already exists + IdentityAlreadyExist, + /// the pair (litentry-account, identity) doesn't exist + IdentityNotExist, + /// the identity was not linked before verification + IdentityNotLinked, + /// a verification reqeust comes too early + VerificationRequestTooEarly, + /// a verification reqeust comes too late + VerificationRequestTooLate, + } + + /// user shielding key is per Litentry account + #[pallet::storage] + #[pallet::getter(fn user_shielding_keys)] + pub type UserShieldingKeys = + StorageMap<_, Blake2_128Concat, T::AccountId, UserShieldingKeyType, OptionQuery>; + + /// challenge code is per Litentry account + identity + #[pallet::storage] + #[pallet::getter(fn challenge_codes)] + pub type ChallengeCodes = StorageDoubleMap< + _, + Blake2_128Concat, + T::AccountId, + Blake2_128Concat, + Identity, + ChallengeCode, + OptionQuery, + >; + + /// ID graph is per Litentry account + identity + #[pallet::storage] + #[pallet::getter(fn id_graphs)] + pub type IDGraphs = StorageDoubleMap< + _, + Blake2_128Concat, + T::AccountId, + Blake2_128Concat, + Identity, + IdentityContext, + OptionQuery, + >; + + #[pallet::call] + impl Pallet { + #[pallet::weight(15_000_000)] + pub fn set_user_shielding_key( + origin: OriginFor, + who: T::AccountId, + key: UserShieldingKeyType, + ) -> DispatchResult { + T::ManageOrigin::ensure_origin(origin)?; + // we don't care about the current key + UserShieldingKeys::::insert(&who, key); + Self::deposit_event(Event::UserShieldingKeySet { who, key }); + Ok(()) + } + + #[pallet::weight(15_000_000)] + pub fn set_challenge_code( + origin: OriginFor, + who: T::AccountId, + identity: Identity, + code: ChallengeCode, + ) -> DispatchResult { + T::ManageOrigin::ensure_origin(origin)?; + // we don't care if it has already associated with any challenge code + ChallengeCodes::::insert(&who, &identity, code); + Self::deposit_event(Event::ChallengeCodeSet { who, identity, code }); + Ok(()) + } + + #[pallet::weight(15_000_000)] + pub fn remove_challenge_code( + origin: OriginFor, + who: T::AccountId, + identity: Identity, + ) -> DispatchResult { + T::ManageOrigin::ensure_origin(origin)?; + ensure!( + ChallengeCodes::::contains_key(&who, &identity), + Error::::ChallengeCodeNotExist + ); + ChallengeCodes::::remove(&who, &identity); + Self::deposit_event(Event::ChallengeCodeRemoved { who, identity }); + Ok(()) + } + + #[pallet::weight(15_000_000)] + pub fn link_identity( + origin: OriginFor, + who: T::AccountId, + identity: Identity, + metadata: Option>, + linking_request_block: ParentchainBlockNumber, + ) -> DispatchResult { + T::ManageOrigin::ensure_origin(origin)?; + ensure!( + !IDGraphs::::contains_key(&who, &identity), + Error::::IdentityAlreadyExist + ); + let context = IdentityContext { + metadata, + linking_request_block: Some(linking_request_block), + ..Default::default() + }; + IDGraphs::::insert(&who, &identity, context); + Self::deposit_event(Event::IdentityLinked { who, identity }); + Ok(()) + } + + #[pallet::weight(15_000_000)] + pub fn unlink_identity( + origin: OriginFor, + who: T::AccountId, + identity: Identity, + ) -> DispatchResult { + T::ManageOrigin::ensure_origin(origin)?; + ensure!(IDGraphs::::contains_key(&who, &identity), Error::::IdentityNotExist); + IDGraphs::::remove(&who, &identity); + Self::deposit_event(Event::IdentityUnlinked { who, identity }); + Ok(()) + } + + #[pallet::weight(15_000_000)] + pub fn verify_identity( + origin: OriginFor, + who: T::AccountId, + identity: Identity, + verification_request_block: ParentchainBlockNumber, + ) -> DispatchResult { + T::ManageOrigin::ensure_origin(origin)?; + IDGraphs::::try_mutate(&who, &identity, |context| -> DispatchResult { + let mut c = context.take().ok_or(Error::::IdentityNotExist)?; + + if let Some(b) = c.linking_request_block { + ensure!( + b <= verification_request_block, + Error::::VerificationRequestTooEarly + ); + ensure!( + verification_request_block - b <= T::MaxVerificationDelay::get(), + Error::::VerificationRequestTooLate + ); + c.is_verified = true; + c.verification_request_block = Some(verification_request_block); + *context = Some(c); + Ok(()) + } else { + Err(Error::::IdentityNotLinked.into()) + } + }) + } + } + + impl Pallet { + pub fn get_identity_and_identity_context( + who: &T::AccountId, + ) -> Vec<(Identity, IdentityContext)> { + IDGraphs::iter_prefix(who).collect::>() + } + } +} diff --git a/tee-worker/litentry/pallets/identity-management/src/mock.rs b/tee-worker/litentry/pallets/identity-management/src/mock.rs new file mode 100644 index 0000000000..6e0bb94799 --- /dev/null +++ b/tee-worker/litentry/pallets/identity-management/src/mock.rs @@ -0,0 +1,123 @@ +// Copyright 2020-2022 Litentry Technologies GmbH. +// This file is part of Litentry. +// +// Litentry is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Litentry is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Litentry. If not, see . + +use crate as pallet_tee_identity_management; +use frame_support::{ + ord_parameter_types, parameter_types, + traits::{ConstU128, ConstU16, ConstU32}, +}; +use frame_system as system; +use frame_system::EnsureSignedBy; +use litentry_primitives::{ + Identity, IdentityHandle, IdentityWebType, SubstrateNetwork, Web3Network, +}; +use sp_core::H256; +use sp_runtime::{ + testing::Header, + traits::{BlakeTwo256, IdentityLookup}, +}; + +type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; +type Block = frame_system::mocking::MockBlock; +pub type Balance = u128; + +// Configure a mock runtime to test the pallet. +frame_support::construct_runtime!( + pub enum Test where + Block = Block, + NodeBlock = Block, + UncheckedExtrinsic = UncheckedExtrinsic, + { + System: frame_system, + Balances: pallet_balances, + IMT: pallet_tee_identity_management, + } +); + +parameter_types! { + pub const BlockHashCount: u64 = 250; +} + +impl system::Config for Test { + type BaseCallFilter = frame_support::traits::Everything; + type BlockWeights = (); + type BlockLength = (); + type DbWeight = (); + type Origin = Origin; + type Call = Call; + type Index = u64; + type BlockNumber = u64; + type Hash = H256; + type Hashing = BlakeTwo256; + type AccountId = u64; + type Lookup = IdentityLookup; + type Header = Header; + type Event = Event; + type BlockHashCount = BlockHashCount; + type Version = (); + type PalletInfo = PalletInfo; + type AccountData = pallet_balances::AccountData; + type OnNewAccount = (); + type OnKilledAccount = (); + type SystemWeightInfo = (); + type SS58Prefix = ConstU16<31>; + type OnSetCode = (); + type MaxConsumers = ConstU32<16>; +} + +impl pallet_balances::Config for Test { + type MaxLocks = ConstU32<50>; + type MaxReserves = (); + type ReserveIdentifier = [u8; 8]; + type Balance = Balance; // the type that is relevant to us + type Event = Event; + type DustRemoval = (); + type ExistentialDeposit = ConstU128<1>; + type AccountStore = System; + type WeightInfo = (); +} + +ord_parameter_types! { + pub const One: u64 = 1; +} + +impl pallet_tee_identity_management::Config for Test { + type Event = Event; + type ManageOrigin = EnsureSignedBy; + type MaxMetadataLength = ConstU32<128>; + type MaxVerificationDelay = ConstU32<2>; +} + +// account id of Alice 0xd43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d +pub const ALICE_KEY: [u8; 32] = [ + 0xd4, 0x35, 0x93, 0xc7, 0x15, 0xfd, 0xd3, 0x1c, 0x61, 0x14, 0x1a, 0xbd, 0x04, 0xa9, 0x9f, 0xd6, + 0x82, 0x2c, 0x85, 0x58, 0x85, 0x4c, 0xcd, 0xe3, 0x9a, 0x56, 0x84, 0xe7, 0xa5, 0x6d, 0xa2, 0x7d, +]; + +pub const ALICE_WEB3_IDENTITY: Identity = Identity { + web_type: IdentityWebType::Web3(Web3Network::Substrate(SubstrateNetwork::Polkadot)), + handle: IdentityHandle::Address32(ALICE_KEY), +}; + +pub fn new_test_ext() -> sp_io::TestExternalities { + let t = system::GenesisConfig::default().build_storage::().unwrap(); + + let mut ext = sp_io::TestExternalities::new(t); + ext.execute_with(|| { + System::set_block_number(1); + }); + ext +} diff --git a/tee-worker/litentry/pallets/identity-management/src/tests.rs b/tee-worker/litentry/pallets/identity-management/src/tests.rs new file mode 100644 index 0000000000..6a300c4c34 --- /dev/null +++ b/tee-worker/litentry/pallets/identity-management/src/tests.rs @@ -0,0 +1,217 @@ +// Copyright 2020-2022 Litentry Technologies GmbH. +// This file is part of Litentry. +// +// Litentry is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Litentry is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Litentry. If not, see . + +use crate::{ + identity_context::IdentityContext, mock::*, Error, MetadataOf, ParentchainBlockNumber, + UserShieldingKeyType, +}; +use frame_support::{assert_noop, assert_ok}; +use litentry_primitives::{ + Identity, IdentityHandle, IdentityString, IdentityWebType, Web2Network, USER_SHIELDING_KEY_LEN, +}; +#[test] +fn set_user_shielding_key_works() { + new_test_ext().execute_with(|| { + let shielding_key: UserShieldingKeyType = [0u8; USER_SHIELDING_KEY_LEN]; + assert_eq!(IMT::user_shielding_keys(2), None); + assert_ok!(IMT::set_user_shielding_key(Origin::signed(1), 2, shielding_key.clone())); + assert_eq!(IMT::user_shielding_keys(2), Some(shielding_key.clone())); + System::assert_last_event(Event::IMT(crate::Event::UserShieldingKeySet { + who: 2, + key: shielding_key, + })); + }); +} + +#[test] +fn link_identity_works() { + new_test_ext().execute_with(|| { + let metadata: MetadataOf = vec![0u8; 16].try_into().unwrap(); + assert_ok!(IMT::link_identity( + Origin::signed(1), + 2, + ALICE_WEB3_IDENTITY.clone(), + Some(metadata.clone()), + 1 + )); + assert_eq!( + IMT::id_graphs(2, ALICE_WEB3_IDENTITY).unwrap(), + IdentityContext { + metadata: Some(metadata), + linking_request_block: Some(1), + verification_request_block: None, + is_verified: false, + } + ); + }); +} + +#[test] +fn unlink_identity_works() { + new_test_ext().execute_with(|| { + let metadata: MetadataOf = vec![0u8; 16].try_into().unwrap(); + assert_noop!( + IMT::unlink_identity(Origin::signed(1), 2, ALICE_WEB3_IDENTITY.clone()), + Error::::IdentityNotExist + ); + assert_ok!(IMT::link_identity( + Origin::signed(1), + 2, + ALICE_WEB3_IDENTITY.clone(), + Some(metadata.clone()), + 1 + )); + assert_eq!( + IMT::id_graphs(2, ALICE_WEB3_IDENTITY.clone()).unwrap(), + IdentityContext { + metadata: Some(metadata), + linking_request_block: Some(1), + verification_request_block: None, + is_verified: false, + } + ); + assert_ok!(IMT::unlink_identity(Origin::signed(1), 2, ALICE_WEB3_IDENTITY.clone())); + assert_eq!(IMT::id_graphs(2, ALICE_WEB3_IDENTITY), None); + }); +} + +#[test] +fn verify_identity_works() { + new_test_ext().execute_with(|| { + let metadata: MetadataOf = vec![0u8; 16].try_into().unwrap(); + assert_ok!(IMT::link_identity( + Origin::signed(1), + 2, + ALICE_WEB3_IDENTITY.clone(), + Some(metadata.clone()), + 1 + )); + assert_ok!(IMT::verify_identity(Origin::signed(1), 2, ALICE_WEB3_IDENTITY.clone(), 1)); + assert_eq!( + IMT::id_graphs(2, ALICE_WEB3_IDENTITY).unwrap(), + IdentityContext { + metadata: Some(metadata), + linking_request_block: Some(1), + verification_request_block: Some(1), + is_verified: true, + } + ); + }); +} + +#[test] +fn get_identity_and_identity_context_works() { + new_test_ext().execute_with(|| { + let metadata3: MetadataOf = vec![0u8; 16].try_into().unwrap(); + assert_ok!(IMT::link_identity( + Origin::signed(1), + 2, + ALICE_WEB3_IDENTITY.clone(), + Some(metadata3.clone()), + 3 + )); + assert_ok!(IMT::verify_identity(Origin::signed(1), 2, ALICE_WEB3_IDENTITY.clone(), 3)); + + let alice_web2_identity: Identity = Identity { + web_type: IdentityWebType::Web2(Web2Network::Twitter), + handle: IdentityHandle::String( + IdentityString::try_from("litentry".as_bytes().to_vec()).unwrap(), + ), + }; + let metadata2: MetadataOf = vec![0u8; 16].try_into().unwrap(); + assert_ok!(IMT::link_identity( + Origin::signed(1), + 2, + alice_web2_identity.clone(), + Some(metadata2.clone()), + 2 + )); + assert_ok!(IMT::verify_identity(Origin::signed(1), 2, alice_web2_identity.clone(), 2)); + + let did_contex = IMT::get_identity_and_identity_context(&2); + assert_eq!(did_contex.len(), 2); + }); +} + +#[test] +fn verify_identity_fails_when_too_early() { + new_test_ext().execute_with(|| { + const LINKNIG_REQUEST_BLOCK: ParentchainBlockNumber = 2; + const VERIFICATION_REQUEST_BLOCK: ParentchainBlockNumber = 1; + + let metadata: MetadataOf = vec![0u8; 16].try_into().unwrap(); + assert_ok!(IMT::link_identity( + Origin::signed(1), + 2, + ALICE_WEB3_IDENTITY.clone(), + Some(metadata.clone()), + LINKNIG_REQUEST_BLOCK + )); + assert_noop!( + IMT::verify_identity( + Origin::signed(1), + 2, + ALICE_WEB3_IDENTITY.clone(), + VERIFICATION_REQUEST_BLOCK + ), + Error::::VerificationRequestTooEarly + ); + assert_eq!( + IMT::id_graphs(2, ALICE_WEB3_IDENTITY).unwrap(), + IdentityContext { + metadata: Some(metadata), + linking_request_block: Some(LINKNIG_REQUEST_BLOCK), + verification_request_block: None, + is_verified: false, + } + ); + }); +} + +#[test] +fn verify_identity_fails_when_too_late() { + new_test_ext().execute_with(|| { + const LINKNIG_REQUEST_BLOCK: ParentchainBlockNumber = 1; + const VERIFICATION_REQUEST_BLOCK: ParentchainBlockNumber = 5; + + let metadata: MetadataOf = vec![0u8; 16].try_into().unwrap(); + assert_ok!(IMT::link_identity( + Origin::signed(1), + 2, + ALICE_WEB3_IDENTITY.clone(), + Some(metadata.clone()), + LINKNIG_REQUEST_BLOCK + )); + assert_noop!( + IMT::verify_identity( + Origin::signed(1), + 2, + ALICE_WEB3_IDENTITY.clone(), + VERIFICATION_REQUEST_BLOCK + ), + Error::::VerificationRequestTooLate + ); + assert_eq!( + IMT::id_graphs(2, ALICE_WEB3_IDENTITY).unwrap(), + IdentityContext { + metadata: Some(metadata), + linking_request_block: Some(LINKNIG_REQUEST_BLOCK), + verification_request_block: None, + is_verified: false, + } + ); + }); +} diff --git a/tee-worker/litentry/primitives/Cargo.toml b/tee-worker/litentry/primitives/Cargo.toml new file mode 100644 index 0000000000..cd60d4b27f --- /dev/null +++ b/tee-worker/litentry/primitives/Cargo.toml @@ -0,0 +1,36 @@ +[package] +authors = ["Litentry Dev"] +edition = "2021" +name = "litentry-primitives" +version = "0.1.0" + +[dependencies] +codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } +hex = { version = "0.4.3", default-features = false, optional = true } +scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } +serde = { version = "1.0", default-features = false, features = ["alloc", "derive"] } +serde_json = { version = "1.0", default-features = false, features = ["alloc"] } +sp-core = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.29", default-features = false } +sp-runtime = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.29", default-features = false } +sp-std = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.29", default-features = false } + +# sgx dependencies +sgx_tstd = { git = "https://github.com/apache/teaclave-sgx-sdk.git", branch = "master", optional = true, features = ["net", "thread"] } + +parentchain-primitives = { package = "primitives", git = "https://github.com/litentry/litentry-parachain.git", branch = "tee-dev", default-features = false } + +[features] +default = ["std"] +production = [] +sgx = [ + "sgx_tstd", +] +std = [ + "hex/std", + "serde/std", + "serde_json/std", + "sp-core/std", + "sp-std/std", + "sp-runtime/std", + "parentchain-primitives/std", +] diff --git a/tee-worker/litentry/primitives/src/assertion.rs b/tee-worker/litentry/primitives/src/assertion.rs new file mode 100644 index 0000000000..d61ffb4779 --- /dev/null +++ b/tee-worker/litentry/primitives/src/assertion.rs @@ -0,0 +1,45 @@ +// Copyright 2020-2022 Litentry Technologies GmbH. +// This file is part of Litentry. +// +// Litentry is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Litentry is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Litentry. If not, see . + +// This file includes the predefined rulesets and the corresponding parameters +// when requesting VCs. +// +// See: https://www.notion.so/litentry/Expected-parameters-in-predefined-rulesets-14f74928aa2b43509167da12a3e75507 + +use codec::{Decode, Encode, MaxEncodedLen}; +use scale_info::TypeInfo; +use sp_runtime::{traits::ConstU32, BoundedVec}; + +type Balance = u128; +type MaxStringLength = ConstU32<64>; +pub type ParameterString = BoundedVec; + +#[derive(Encode, Decode, Clone, Debug, PartialEq, Eq, TypeInfo, MaxEncodedLen)] +pub enum Assertion { + A1, + A2(ParameterString, ParameterString), // (guild_id, user_id) + A3(ParameterString, ParameterString), // (guild_id, user_id) + A4, + A5(ParameterString, ParameterString), // (twitter_account, tweet_id) + A6, + A7(Balance, u32), // (DOT_amount, year) + A8(u64), // (tx_amount) + A9, + A10(Balance, u32), // (WBTC_amount, year) + A11(Balance, u32), // (ETH_amount, year) + A12(Balance, u32), // (LIT_amount, year) + A13(u32), // (Karma_amount) - TODO: unsupported +} diff --git a/tee-worker/litentry/primitives/src/ethereum_signature.rs b/tee-worker/litentry/primitives/src/ethereum_signature.rs new file mode 100644 index 0000000000..6750093f97 --- /dev/null +++ b/tee-worker/litentry/primitives/src/ethereum_signature.rs @@ -0,0 +1,72 @@ +// Copyright 2020-2022 Litentry Technologies GmbH. +// This file is part of Litentry. +// +// Litentry is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Litentry is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Litentry. If not, see . +#[cfg(feature = "std")] +use serde::{de, Deserialize, Deserializer, Serialize, Serializer}; + +use codec::{Decode, Encode, MaxEncodedLen}; +use scale_info::TypeInfo; + +#[derive(Encode, Decode, MaxEncodedLen, TypeInfo, PartialEq, Eq, Clone, Debug)] +pub struct EthereumSignature(pub [u8; 65]); + +impl TryFrom<&[u8]> for EthereumSignature { + type Error = (); + + fn try_from(data: &[u8]) -> Result { + if data.len() == 65 { + let mut inner = [0u8; 65]; + inner.copy_from_slice(data); + Ok(EthereumSignature(inner)) + } else { + Err(()) + } + } +} + +#[cfg(feature = "std")] +impl Serialize for EthereumSignature { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + serializer.serialize_str(&hex::encode(self)) + } +} + +#[cfg(feature = "std")] +impl<'de> Deserialize<'de> for EthereumSignature { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let signature_hex = hex::decode(&String::deserialize(deserializer)?) + .map_err(|e| de::Error::custom(format!("{:?}", e)))?; + EthereumSignature::try_from(signature_hex.as_ref()) + .map_err(|e| de::Error::custom(format!("{:?}", e))) + } +} + +impl AsRef<[u8; 65]> for EthereumSignature { + fn as_ref(&self) -> &[u8; 65] { + &self.0 + } +} + +impl AsRef<[u8]> for EthereumSignature { + fn as_ref(&self) -> &[u8] { + &self.0[..] + } +} diff --git a/tee-worker/litentry/primitives/src/identity.rs b/tee-worker/litentry/primitives/src/identity.rs new file mode 100644 index 0000000000..6aa620d568 --- /dev/null +++ b/tee-worker/litentry/primitives/src/identity.rs @@ -0,0 +1,163 @@ +// Copyright 2020-2022 Litentry Technologies GmbH. +// This file is part of Litentry. +// +// Litentry is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Litentry is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Litentry. If not, see . + +#[cfg(all(not(feature = "std"), feature = "sgx"))] +extern crate sgx_tstd as std; + +#[cfg(any(feature = "std", feature = "sgx"))] +use std::format; + +#[cfg(all(not(feature = "sgx"), feature = "std"))] +use serde::{Deserialize, Serialize}; +#[cfg(any(feature = "std", feature = "sgx"))] +use sp_core::hexdisplay::HexDisplay; +#[cfg(any(feature = "std", feature = "sgx"))] +use std::vec::Vec; + +use codec::{Decode, Encode, MaxEncodedLen}; +use scale_info::TypeInfo; +use sp_runtime::{traits::ConstU32, BoundedVec}; + +pub type MaxStringLength = ConstU32<64>; +pub type IdentityString = BoundedVec; + +#[derive(Encode, Decode, Clone, Debug, PartialEq, Eq, TypeInfo, MaxEncodedLen)] +#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] +pub enum SubstrateNetwork { + Polkadot, + Kusama, + Litentry, + Litmus, +} + +#[derive(Encode, Decode, Clone, Debug, PartialEq, Eq, TypeInfo, MaxEncodedLen)] +#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] +pub enum EvmNetwork { + Ethereum, + BSC, +} + +#[derive(Encode, Decode, Clone, Debug, PartialEq, Eq, TypeInfo, MaxEncodedLen)] +#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] +pub enum Web3Network { + Substrate(SubstrateNetwork), + Evm(EvmNetwork), +} + +#[derive(Encode, Decode, Clone, Debug, PartialEq, Eq, TypeInfo, MaxEncodedLen)] +#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] +pub enum Web2Network { + Twitter, + Discord, + Github, +} + +#[derive(Encode, Decode, Clone, Debug, PartialEq, Eq, TypeInfo, MaxEncodedLen)] +#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] +pub enum IdentityWebType { + Web2(Web2Network), + Web3(Web3Network), +} + +#[derive(Encode, Decode, Clone, Debug, PartialEq, Eq, TypeInfo, MaxEncodedLen)] +#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] +pub enum IdentityHandle { + Address32([u8; 32]), + /// Its a 20 byte representation. + Address20([u8; 20]), + String(IdentityString), +} + +#[derive(Encode, Decode, Clone, Debug, PartialEq, Eq, TypeInfo, MaxEncodedLen)] +#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] +pub struct Identity { + pub web_type: IdentityWebType, + pub handle: IdentityHandle, +} + +impl Identity { + #[cfg(any(feature = "std", feature = "sgx"))] + pub fn flat(&self) -> Vec { + let mut display = match &self.web_type { + IdentityWebType::Web3(Web3Network::Substrate(sub)) => + format!("did:{:?}:web3:substrate:", sub) + .to_ascii_lowercase() + .as_bytes() + .to_vec(), + IdentityWebType::Web3(Web3Network::Evm(evm)) => + format!("did:{:?}:web3:evm:", evm).to_ascii_lowercase().as_bytes().to_vec(), + IdentityWebType::Web2(web2) => + format!("did:{:?}:web2:_:", web2).to_ascii_lowercase().as_bytes().to_vec(), + }; + let mut suffix: Vec = match &self.handle { + IdentityHandle::String(inner) => inner.to_vec(), + IdentityHandle::Address32(inner) => + format!("0x{}", HexDisplay::from(inner)).as_bytes().to_vec(), + IdentityHandle::Address20(inner) => + format!("0x{}", HexDisplay::from(inner)).as_bytes().to_vec(), + }; + display.append(&mut suffix); + display + } + + pub fn is_web2(&self) -> bool { + match &self.web_type { + IdentityWebType::Web2(_) => true, + IdentityWebType::Web3(_) => false, + } + } + + pub fn is_web3(&self) -> bool { + match &self.web_type { + IdentityWebType::Web2(_) => false, + IdentityWebType::Web3(_) => true, + } + } +} + +#[cfg(test)] +mod tests { + use crate::{ + Identity, IdentityHandle, IdentityString, IdentityWebType, SubstrateNetwork, Web2Network, + Web3Network, + }; + use sp_core::Pair; + use std::string; + + #[test] + fn identity() { + let sub_pair = sp_core::sr25519::Pair::from_string("//Alice", None).unwrap(); + // let eth_pair = sp_core::ed25519::Pair::from_string("//Alice", None).unwrap(); + let polkadot_identity = Identity { + web_type: IdentityWebType::Web3(Web3Network::Substrate(SubstrateNetwork::Polkadot)), + handle: IdentityHandle::Address32(sub_pair.public().0), + }; + let twitter_identity = Identity { + web_type: IdentityWebType::Web2(Web2Network::Twitter), + handle: IdentityHandle::String( + IdentityString::try_from("litentry".as_bytes().to_vec()).unwrap(), + ), + }; + assert_eq!( + "did:polkadot:web3:substrate:0xd43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d", + string::String::from_utf8(polkadot_identity.flat()).unwrap() + ); + assert_eq!( + "did:twitter:web2:_:litentry", + string::String::from_utf8(twitter_identity.flat()).unwrap() + ); + } +} diff --git a/tee-worker/litentry/primitives/src/lib.rs b/tee-worker/litentry/primitives/src/lib.rs new file mode 100644 index 0000000000..0e6bae9dd2 --- /dev/null +++ b/tee-worker/litentry/primitives/src/lib.rs @@ -0,0 +1,37 @@ +// Copyright 2020-2022 Litentry Technologies GmbH. +// This file is part of Litentry. +// +// Litentry is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Litentry is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Litentry. If not, see . + +#![cfg_attr(not(feature = "std"), no_std)] +#[cfg(all(not(feature = "std"), feature = "sgx"))] +extern crate sgx_tstd as std; + +mod ethereum_signature; +mod identity; +// mod trusted_call; +mod assertion; +mod validation_data; + +pub use ethereum_signature::*; +pub use identity::*; +pub use parentchain_primitives::{ + AesOutput, BlockNumber as ParentchainBlockNumber, UserShieldingKeyType, USER_SHIELDING_KEY_LEN, + USER_SHIELDING_KEY_NONCE_LEN, USER_SHIELDING_KEY_TAG_LEN, +}; +// pub use trusted_call::*; +pub use assertion::*; +pub use validation_data::*; + +pub type ChallengeCode = [u8; 16]; diff --git a/tee-worker/litentry/primitives/src/validation_data.rs b/tee-worker/litentry/primitives/src/validation_data.rs new file mode 100644 index 0000000000..74e089f48f --- /dev/null +++ b/tee-worker/litentry/primitives/src/validation_data.rs @@ -0,0 +1,84 @@ +// Copyright 2020-2022 Litentry Technologies GmbH. +// This file is part of Litentry. +// +// Litentry is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Litentry is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Litentry. If not, see . + +#[cfg(feature = "std")] +use serde::{Deserialize, Serialize}; + +use crate::ethereum_signature::EthereumSignature; +use codec::{Decode, Encode, MaxEncodedLen}; +use scale_info::TypeInfo; +use sp_core::{ecdsa, ed25519, sr25519}; +use sp_runtime::{traits::ConstU32, BoundedVec}; + +pub type MaxStringLength = ConstU32<64>; +pub type ValidationString = BoundedVec; + +#[derive(Encode, Decode, Clone, Debug, PartialEq, Eq, TypeInfo, MaxEncodedLen)] +#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] +pub enum IdentityMultiSignature { + /// An Ed25519 signature. + Ed25519(ed25519::Signature), + /// An Sr25519 signature. + Sr25519(sr25519::Signature), + /// An ECDSA/SECP256k1 signature. + Ecdsa(ecdsa::Signature), + /// An ECDSA/keccak256 signature. An Ethereum signature. hash message with keccak256 + Ethereum(EthereumSignature), +} + +#[derive(Encode, Decode, Clone, Debug, PartialEq, Eq, TypeInfo, MaxEncodedLen)] +#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] +pub struct TwitterValidationData { + pub tweet_id: ValidationString, +} + +#[derive(Encode, Decode, Clone, Debug, PartialEq, Eq, TypeInfo, MaxEncodedLen)] +#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] +pub struct DiscordValidationData { + pub channel_id: ValidationString, + pub message_id: ValidationString, + pub guild_id: ValidationString, +} + +#[derive(Encode, Decode, Clone, Debug, PartialEq, Eq, TypeInfo, MaxEncodedLen)] +#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] +pub struct Web3CommonValidationData { + pub message: ValidationString, // or String if under std + pub signature: IdentityMultiSignature, +} + +#[derive(Encode, Decode, Clone, Debug, PartialEq, Eq, TypeInfo, MaxEncodedLen)] +#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] +#[allow(non_camel_case_types)] +pub enum Web2ValidationData { + Twitter(TwitterValidationData), + Discord(DiscordValidationData), +} + +#[derive(Encode, Decode, Clone, Debug, PartialEq, Eq, TypeInfo, MaxEncodedLen)] +#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] +#[allow(non_camel_case_types)] +pub enum Web3ValidationData { + Substrate(Web3CommonValidationData), + Evm(Web3CommonValidationData), +} + +#[derive(Encode, Decode, Clone, Debug, PartialEq, Eq, TypeInfo, MaxEncodedLen)] +#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] +pub enum ValidationData { + Web2(Web2ValidationData), + Web3(Web3ValidationData), +} diff --git a/tee-worker/local-setup/README.md b/tee-worker/local-setup/README.md new file mode 100644 index 0000000000..0c9d986d9e --- /dev/null +++ b/tee-worker/local-setup/README.md @@ -0,0 +1,37 @@ +# How to use the local-setup + +## Prerequisite +- worker built with ` SGX_MODE=SW make` +- integritee-node built with `cargo build --release --features skip-ias-check` + +In case you have +- a sgx hardware and compile the worker with `SGX_MODE=HW` (default mode) +- a valid intel IAS key (development key is fine) + +you can omit the `--features skip-ias-check` when building the node, but you must not use the subcommand flag `--skip-ra` in the json file (see [`simple-config.json`](simple-config.json)) you're using to start the worker. + +## Steps +Adapt or create your own config file, as in the example of [`simple-config.json`](simple-config.json). Be mindful of the ports in case you're running the script on a server multiple people are working on. + +### Launch worker and node in terminal one +You can launch the workers and the node with: +```bash +./local-setup/launch.py ./local-setup/simple-config.json +``` +wait a little until all workers have been launched. You can stop the worker and node simply by pressing `Ctrl + c`. + +### Open a second terminal to show logs +```bash +cd local-setup +./tmux_logger.sh +``` + +You can remove the tmux session of the script by running +```bash +tmux kill-session -t integritee_logger +``` +### Open a third terminal to run a demo +```bash +cd /cli +./demo_shielding_unshielding.sh -p 99xx -P 20xx +``` diff --git a/tee-worker/local-setup/__init__.py b/tee-worker/local-setup/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tee-worker/local-setup/benchmark-config.json b/tee-worker/local-setup/benchmark-config.json new file mode 100644 index 0000000000..369df363e8 --- /dev/null +++ b/tee-worker/local-setup/benchmark-config.json @@ -0,0 +1,38 @@ +{ + "node": { + "bin": "../integritee-node/target/release/integritee-node", + "flags": [ + "--tmp", + "--dev", + "-lruntime=info", + "--ws-port", + "9930", + "--port", + "30330", + "--rpc-port", + "8930" + ] + }, + "workers": [ + { + "source": "bin", + "flags": [ + "--clean-reset", + "-P", + "2030", + "-p", + "9930", + "-r", + "3430", + "-w", + "2031", + "-h", + "4530" + ], + "subcommand_flags": [ + "--skip-ra", + "--dev" + ] + } + ] +} diff --git a/tee-worker/local-setup/github-action-config-one-worker.json b/tee-worker/local-setup/github-action-config-one-worker.json new file mode 100644 index 0000000000..cd50dd502e --- /dev/null +++ b/tee-worker/local-setup/github-action-config-one-worker.json @@ -0,0 +1,22 @@ +{ + "workers": [ + { + "source": "bin", + "flags": [ + "--clean-reset", + "-P", + "2000", + "-w", + "2001", + "-r", + "3443", + "-h", + "4545" + ], + "subcommand_flags": [ + "--skip-ra", + "--dev" + ] + } + ] +} diff --git a/tee-worker/local-setup/github-action-config-rococo.json b/tee-worker/local-setup/github-action-config-rococo.json new file mode 100644 index 0000000000..9df7939fe5 --- /dev/null +++ b/tee-worker/local-setup/github-action-config-rococo.json @@ -0,0 +1,49 @@ +{ + "workers": [ + { + "source": "bin", + "flags": [ + "--clean-reset", + "-P", + "2000", + "-w", + "2001", + "-r", + "3443", + "-h", + "4545", + "-u", + "wss://parachain-eu-0.staging.litentry.io", + "-p", + "443" + ], + "subcommand_flags": [ + "--skip-ra", + "--dev" + ] + }, + { + "source": "bin", + "flags": [ + "--clean-reset", + "-P", + "3000", + "-w", + "3001", + "-r", + "3444", + "-h", + "4546", + "-u", + "wss://parachain-eu-0.staging.litentry.io", + "-p", + "443" + ], + "subcommand_flags": [ + "--skip-ra", + "--dev", + "--request-state" + ] + } + ] +} diff --git a/tee-worker/local-setup/github-action-config.json b/tee-worker/local-setup/github-action-config.json new file mode 100644 index 0000000000..c681424c1e --- /dev/null +++ b/tee-worker/local-setup/github-action-config.json @@ -0,0 +1,41 @@ +{ + "workers": [ + { + "source": "bin", + "flags": [ + "--clean-reset", + "-P", + "2000", + "-w", + "2001", + "-r", + "3443", + "-h", + "4545" + ], + "subcommand_flags": [ + "--skip-ra", + "--dev" + ] + }, + { + "source": "bin", + "flags": [ + "--clean-reset", + "-P", + "3000", + "-w", + "3001", + "-r", + "3444", + "-h", + "4546" + ], + "subcommand_flags": [ + "--skip-ra", + "--dev", + "--request-state" + ] + } + ] +} diff --git a/tee-worker/local-setup/launch-rococo-staging.py b/tee-worker/local-setup/launch-rococo-staging.py new file mode 100755 index 0000000000..6d350e9360 --- /dev/null +++ b/tee-worker/local-setup/launch-rococo-staging.py @@ -0,0 +1,76 @@ +#!/usr/bin/env python3 +""" +Launch handily a local dev setup consisting of one integritee-node and some workers. + +Example usage: `./local-setup/launch.py /local-setup/simple-config.py` + +The node and workers logs are piped to `./log/node.log` etc. folder in the current-working dir. + +run: `cd local-setup && tmux_logger.sh` to automatically `tail -f` these three logs. + +""" +import argparse +import json +import signal +from subprocess import Popen, PIPE, STDOUT, run +import sys +from time import sleep +from typing import Union, IO + +from py.worker import Worker +from py.helpers import GracefulKiller, mkdir_p + +log_dir = 'log' +mkdir_p(log_dir) +node_log = open(f'{log_dir}/node.log', 'w+') + + +def setup_worker(work_dir: str, source_dir: str, std_err: Union[None, int, IO]): + print(f'Setting up worker in {work_dir}') + print(f'Copying files from {source_dir}') + worker = Worker(cwd=work_dir, source_dir=source_dir, std_err=std_err) + worker.init_clean() + print('Initialized worker.') + return worker + + +def run_worker(config, i: int): + log = open(f'{log_dir}/worker{i}.log', 'w+') + w = setup_worker(f'tmp/w{i}', config["source"], log) + + print(f'Starting worker {i} in background') + return w.run_in_background(log_file=log, flags=config["flags"], subcommand_flags=config["subcommand_flags"]) + + +def main(processes, config_path): + print('Starting litentry-parachain in background') + + with open(config_path) as config_file: + config = json.load(config_file) + + i = 1 + for w_conf in config["workers"]: + processes.append(run_worker(w_conf, i)) + # sleep to prevent nonce clash when bootstrapping the enclave's account + sleep(3) + if i == 1: + # Give worker 1 some time to register itself, otherwise key & state sharing will not work. + # + # litentry: increase the gap between worker launch + # we need a cleaner solution though, see https://github.com/integritee-network/worker/issues/731 + sleep(600) + + i += 1 + + # keep script alive until terminated + signal.pause() + + +if __name__ == '__main__': + parser = argparse.ArgumentParser(description='Run a setup consisting of a node and some workers') + parser.add_argument('config', type=str, help='Config for the node and workers') + args = parser.parse_args() + + process_list = [] + killer = GracefulKiller(process_list) + main(process_list, args.config) diff --git a/tee-worker/local-setup/launch.py b/tee-worker/local-setup/launch.py new file mode 100755 index 0000000000..97d807dc4a --- /dev/null +++ b/tee-worker/local-setup/launch.py @@ -0,0 +1,83 @@ +#!/usr/bin/env python3 +""" +Launch handily a local dev setup consisting of one integritee-node and some workers. + +Example usage: `./local-setup/launch.py /local-setup/simple-config.py` + +The node and workers logs are piped to `./log/node.log` etc. folder in the current-working dir. + +run: `cd local-setup && tmux_logger.sh` to automatically `tail -f` these three logs. + +""" +import argparse +import json +import signal +from subprocess import Popen, PIPE, STDOUT, run +import sys +from time import sleep +from typing import Union, IO + +from py.worker import Worker +from py.helpers import GracefulKiller, mkdir_p + +log_dir = 'log' +mkdir_p(log_dir) +node_log = open(f'{log_dir}/node.log', 'w+') + + +def setup_worker(work_dir: str, source_dir: str, std_err: Union[None, int, IO]): + print(f'Setting up worker in {work_dir}') + print(f'Copying files from {source_dir}') + worker = Worker(cwd=work_dir, source_dir=source_dir, std_err=std_err) + worker.init_clean() + print('Initialized worker.') + return worker + + +def run_worker(config, i: int): + log = open(f'{log_dir}/worker{i}.log', 'w+') + w = setup_worker(f'tmp/w{i}', config["source"], log) + + print(f'Starting worker {i} in background') + return w.run_in_background(log_file=log, flags=config["flags"], subcommand_flags=config["subcommand_flags"]) + + +def main(processes, config_path): + print('Starting litentry-parachain in background') + + with open(config_path) as config_file: + config = json.load(config_file) + + # litentry: start parachain via shell script + # TODO: use Popen and copy the stdout also to node.log + run(['./scripts/litentry/start_parachain.sh']) + + print('Starting litentry-parachain done') + print('----------------------------------------') + + i = 1 + for w_conf in config["workers"]: + processes.append(run_worker(w_conf, i)) + # sleep to prevent nonce clash when bootstrapping the enclave's account + sleep(3) + if i == 1: + # Give worker 1 some time to register itself, otherwise key & state sharing will not work. + # + # litentry: increase the gap between worker launch + # we need a cleaner solution though, see https://github.com/integritee-network/worker/issues/731 + sleep(180) + + i += 1 + + # keep script alive until terminated + signal.pause() + + +if __name__ == '__main__': + parser = argparse.ArgumentParser(description='Run a setup consisting of a node and some workers') + parser.add_argument('config', type=str, help='Config for the node and workers') + args = parser.parse_args() + + process_list = [] + killer = GracefulKiller(process_list) + main(process_list, args.config) diff --git a/tee-worker/local-setup/py/__init__.py b/tee-worker/local-setup/py/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tee-worker/local-setup/py/helpers.py b/tee-worker/local-setup/py/helpers.py new file mode 100644 index 0000000000..ddb84a7f97 --- /dev/null +++ b/tee-worker/local-setup/py/helpers.py @@ -0,0 +1,62 @@ +import os +import signal +import subprocess +import shutil +import sys +from typing import Union, IO + + +def run_subprocess(args, stdout: Union[None, int, IO], stderr: Union[None, int, IO], cwd: str = './'): + """ Wrapper around subprocess that allows a less verbose call """ + + # todo: make configurable + env = dict(os.environ, RUST_LOG='debug,ws=warn,sp_io=warn,substrate_api_client=warn,enclave=debug') + + return subprocess.run(args, stdout=stdout, env=env, cwd=cwd, stderr=stderr).stdout.decode('utf-8').strip() + + +def setup_working_dir(source_dir: str, target_dir: str): + """ Setup the working dir such that the necessary files to run a worker are contained. + + Args: + source_dir: the directory containing the files the be copied. Usually this is the integritee-service/bin dir. + target_dir: the working directory of the worker to be run. + """ + + files_to_copy = ['enclave.signed.so', 'key.txt', 'spid.txt', 'integritee-service'] + [shutil.copy(f'{source_dir}/{f}', f'{target_dir}/{f}') for f in files_to_copy] + + +def mkdir_p(path): + """ Surprisingly, there is no simple function in python to create a dir if it does not exist.""" + return subprocess.run(['mkdir', '-p', path]) + + +class GracefulKiller: + signals = { + signal.SIGINT: 'SIGINT', + signal.SIGTERM: 'SIGTERM' + } + + def __init__(self, processes): + signal.signal(signal.SIGINT, self.exit_gracefully) + signal.signal(signal.SIGTERM, self.exit_gracefully) + self.processes = processes + + def exit_gracefully(self, signum, frame): + print("\nReceived {} signal".format(self.signals[signum])) + print("Cleaning up processes.") + for p in self.processes: + try: + p.kill() + except: + pass + print('Cleaning logs in /tmp dir') + i = 1 + while os.path.isdir(f'/tmp/w{i}'): + shutil.rmtree(f'/tmp/w{i}') + print(f'Removed /tmp/w{i}') + i += 1 + print("Cleaning up litentry-parachain...") + subprocess.run(['./scripts/litentry/stop_parachain.sh', '||', 'true']) + sys.exit(0) diff --git a/tee-worker/local-setup/py/worker.py b/tee-worker/local-setup/py/worker.py new file mode 100644 index 0000000000..da28b23340 --- /dev/null +++ b/tee-worker/local-setup/py/worker.py @@ -0,0 +1,187 @@ +import os +import pathlib +import shutil +import subprocess +from subprocess import Popen, STDOUT +from typing import Union, TextIO, IO + +from .helpers import run_subprocess, setup_working_dir, mkdir_p + + +class Worker: + def __init__(self, + worker_bin: str = './integritee-service', + cwd: str = './', + source_dir: str = './', + std_err: Union[None, int, IO] = STDOUT, + ): + """ + Integritee-service wrapper. + + Args: + worker_bin: Path to the worker bin relative to `cwd` or as absolute path. + + cwd: working directory of the worker. + + source_dir: directory of the source binaries, which will be copied to cwd because + the rust worker looks for files relative to cwd. + + std_err: Were the workers error output will be logged. Note: `std_out` is intended to be unconfigurable + because the prints from the rust worker are often intended to be used in scripts. Making this + configurable, could cause some weird errors. + + + """ + self.cwd = cwd + self.cli = [worker_bin] + self.source_dir = source_dir + self.std_err = std_err + # cache fields + self._mrenclave = None + + def setup_cwd(self): + mkdir_p(self.cwd) + setup_working_dir(self.source_dir, self.cwd) + + def init_clean(self): + """ Purges all db files first and initializes the environment afterwards. """ + print('Copying source files to working directory') + self.setup_cwd() + + def init(self): + """ Initializes the environment such that the worker can be run. """ + print('Initializing worker') + print(self.init_shard()) + print(self.write_signer_pub()) + print(self.write_shielding_pub()) + + def init_shard(self, shard=None): + """ + :param shard: Shard to be initialized. Use mrenclave if `None`. + :return msg: `println!`'s generated by the rust worker. + """ + if not shard: + shard = self.mrenclave() + if self.check_shard_and_prompt_delete(shard): + return 'Shard exists already, will not initialize.' + + return run_subprocess(self.cli + ['init-shard', shard], stdout=subprocess.PIPE, stderr=self.std_err, + cwd=self.cwd) + + def shard_exists(self, shard): + """ Checks if the shard in './shards/[shard]' exists + + :return: exists: True if exists, false otherwise. + """ + return self._shard_path(shard).exists() + + def check_shard_and_prompt_delete(self, shard=None): + """ + Checks if the shard exists and will prompt to delete it. + If shard is none, this will just return. + + :return: + exists: True if file exists at the end of this call. False otherwise. + + """ + if self.shard_exists(shard): + should_purge = input('Do you want to purge existing the shards and sidechain db? [y, n]') + if should_purge == 'y': + self.purge_shards_and_sidechain_db() + print(f'Deleted shard {shard}.') + return False + else: + print('Leaving shard as is.') + return True + else: + return False + + def purge(self): + """ Deletes the light_client_db.bin, the shards and the sidechain_db + """ + self.purge_last_slot_seal() + self.purge_light_client_db() + self.purge_shards_and_sidechain_db() + return self + + def purge_shards_and_sidechain_db(self): + if pathlib.Path(f'{self.cwd}/shards').exists(): + print(f'Purging shards') + shutil.rmtree(pathlib.Path(f'{self.cwd}/shards')) + + if pathlib.Path(f'{self.cwd}/sidechain_db').exists(): + print(f'purging sidechain_db') + shutil.rmtree(pathlib.Path(f'{self.cwd}/sidechain_db')) + + def purge_light_client_db(self): + print(f'purging light_client_db') + for db in pathlib.Path(self.cwd).glob('light_client_db.bin*'): + print(f'remove: {db}') + db.unlink() + + def purge_last_slot_seal(self): + print(f'purging last_slot_seal') + for db in pathlib.Path(self.cwd).glob('last_slot.bin'): + print(f'remove: {db}') + db.unlink() + + def mrenclave(self): + """ Returns the mrenclave and caches it. """ + if not self._mrenclave: + # `std_out` needs to be subProcess.PIPE here! + self._mrenclave = run_subprocess(self.cli + ['mrenclave'], stdout=subprocess.PIPE, stderr=self.std_err, + cwd=self.cwd) + return self._mrenclave + + def write_shielding_pub(self): + return run_subprocess(self.cli + ['shielding-key'], stdout=subprocess.PIPE, stderr=self.std_err, cwd=self.cwd) + + def write_signer_pub(self): + return run_subprocess(self.cli + ['signing-key'], stdout=subprocess.PIPE, stderr=self.std_err, cwd=self.cwd) + + def sync_state(self, flags: [str] = None, skip_ra: bool = False): + """ Returns the keys from another worker. """ + + if skip_ra: + subcommand_flags = ['request-state', '--skip-ra'] + else: + subcommand_flags = ['request-state'] + + return run_subprocess(self.cli + flags + subcommand_flags, stdout=subprocess.PIPE, stderr=self.std_err, cwd=self.cwd) + + def _shard_path(self, shard): + return pathlib.Path(f'{self.cwd}/shards/{shard}') + + def run_in_background(self, log_file: TextIO, flags: [str] = None, subcommand_flags: [str] = None): + """ Runs the worker in the background and writes to the supplied logfile. + + :return: process handle for the spawned background process. + """ + + # Todo: make this configurable + env = dict(os.environ, RUST_LOG='warn,ws=warn,sp_io=error,' + 'substrate_api_client=warn,' + 'jsonrpsee_ws_client=warn,' + 'jsonrpsee_ws_server=warn,' + 'enclave_runtime=warn,' + 'integritee_service=warn,' + 'ita_stf=debug') + + return Popen( + self._assemble_cmd(flags=flags, subcommand_flags=subcommand_flags), + env=env, + stdout=log_file, + stderr=STDOUT, + bufsize=1, + cwd=self.cwd + ) + + def _assemble_cmd(self, flags: [str] = None, subcommand_flags: [str] = None): + """ Assembles the cmd skipping None values. """ + cmd = self.cli + if flags: + cmd += flags + cmd += ['run'] + if subcommand_flags: + cmd += subcommand_flags + return cmd diff --git a/tee-worker/local-setup/simple-config.json b/tee-worker/local-setup/simple-config.json new file mode 100644 index 0000000000..743fc1c46d --- /dev/null +++ b/tee-worker/local-setup/simple-config.json @@ -0,0 +1,59 @@ +{ + "node": { + "bin": "../integritee-node/target/release/integritee-node", + "flags": [ + "--tmp", + "--dev", + "-lruntime=info", + "--ws-port", + "9990", + "--port", + "30390", + "--rpc-port", + "8990" + ] + }, + "workers": [ + { + "source": "bin", + "flags": [ + "--clean-reset", + "-P", + "2090", + "-p", + "9990", + "-r", + "3490", + "-w", + "2091", + "-h", + "4545" + ], + "subcommand_flags": [ + "--skip-ra", + "--dev" + ] + }, + { + "source": "bin", + "flags": [ + "--clean-reset", + "-P", + "3090", + "-p", + "9990", + "-r", + "3590", + "-w", + "3091", + "-h", + "4546" + ], + "subcommand_flags": [ + "--skip-ra", + "--dev", + "--request-state" + ] + } + ] +} diff --git a/tee-worker/local-setup/tmux_logger.sh b/tee-worker/local-setup/tmux_logger.sh new file mode 100755 index 0000000000..20bf3f403e --- /dev/null +++ b/tee-worker/local-setup/tmux_logger.sh @@ -0,0 +1,32 @@ +#!/bin/bash + +# script that setups a tmux session with three panes that attach to the log files +# of the node and the two workers launched by `./launch.py` + +################################################################################# +# If you work with docker: +# +# 1. run: ./launch.py in docker +# 2. open a new bash session in a new window in the running container: +# docker exec -it [container-id] bash +# 3. run this script: ./tmux_logger.sh +################################################################################# + + +if tmux has-session -t integritee_logger ; then + echo "detected existing polkadot logger session, attaching..." +else + # or start it up freshly + tmux new-session -d -s integritee_logger \; \ + split-window -v \; \ + split-window -v \; \ + select-layout even-vertical \; \ + send-keys -t integritee_logger:0.0 'tail -f ../log/node.log' C-m \; \ + send-keys -t integritee_logger:0.1 'tail -f ../log/worker1.log' C-m \; \ + send-keys -t integritee_logger:0.2 'tail -f ../log/worker2.log' C-m + + # Attention: Depending on your tmux conf, indexes may start at 1 + + tmux setw -g mouse on +fi +tmux attach-session -d -t integritee_logger \ No newline at end of file diff --git a/tee-worker/local-setup/tutorial-config.json b/tee-worker/local-setup/tutorial-config.json new file mode 100644 index 0000000000..c653ce63b5 --- /dev/null +++ b/tee-worker/local-setup/tutorial-config.json @@ -0,0 +1,55 @@ +{ + "node": { + "bin": "../integritee-node/target/release/integritee-node", + "flags": [ + "--tmp", + "--dev", + "-lruntime=info", + "--ws-port", + "9944", + "--port", + "30390", + "--rpc-port", + "8990" + ] + }, + "workers": [ + { + "source": "bin", + "flags": [ + "--clean-reset", + "-P", + "2000", + "-p", + "9944", + "-w", + "2001", + "-r", + "3443" + ], + "subcommand_flags": [ + "--skip-ra", + "--dev" + ] + }, + { + "source": "bin", + "flags": [ + "--clean-reset", + "-P", + "3000", + "-p", + "9944", + "-w", + "3001", + "-r", + "3444" + ], + "subcommand_flags": [ + "--skip-ra", + "--dev", + "--request-state" + ] + } + ] +} diff --git a/tee-worker/rust-sgx-sdk/Readme.md b/tee-worker/rust-sgx-sdk/Readme.md new file mode 100644 index 0000000000..4c71699c10 --- /dev/null +++ b/tee-worker/rust-sgx-sdk/Readme.md @@ -0,0 +1,5 @@ +# RUST-SGX-SDK + +This folder contains only the neccessary parts from the [RUST-SGX-SDK](https://github.com/baidu/rust-sgx-sdk). + +All the crates are directly fetched from github. \ No newline at end of file diff --git a/tee-worker/rust-sgx-sdk/buildenv.mk b/tee-worker/rust-sgx-sdk/buildenv.mk new file mode 100644 index 0000000000..ce28be4e55 --- /dev/null +++ b/tee-worker/rust-sgx-sdk/buildenv.mk @@ -0,0 +1,179 @@ +# +# Copyright (C) 2017-2018 Baidu, Inc. All Rights Reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in +# the documentation and/or other materials provided with the +# distribution. +# * Neither the name of Baidu, Inc., nor the names of its +# contributors may be used to endorse or promote products derived +# from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# +# + +CP := /bin/cp -f +MKDIR := mkdir -p +STRIP := strip +OBJCOPY := objcopy + +# clean the content of 'INCLUDE' - this variable will be set by vcvars32.bat +# thus it will cause build error when this variable is used by our Makefile, +# when compiling the code under Cygwin tainted by MSVC environment settings. +INCLUDE := + +# turn on stack protector for SDK +COMMON_FLAGS += -fstack-protector + +ifdef DEBUG + COMMON_FLAGS += -O0 -g -DDEBUG -UNDEBUG +else + COMMON_FLAGS += -O2 -D_FORTIFY_SOURCE=2 -UDEBUG -DNDEBUG +endif + +# turn on compiler warnings as much as possible +COMMON_FLAGS += -Wall -Wextra -Winit-self -Wpointer-arith -Wreturn-type \ + -Waddress -Wsequence-point -Wformat-security \ + -Wmissing-include-dirs -Wfloat-equal -Wundef -Wshadow \ + -Wcast-align -Wconversion -Wredundant-decls + +# additional warnings flags for C +CFLAGS += -Wjump-misses-init -Wstrict-prototypes -Wunsuffixed-float-constants + +# additional warnings flags for C++ +CXXFLAGS += -Wnon-virtual-dtor + +# for static_assert() +CXXFLAGS += -std=c++0x + +.DEFAULT_GOAL := all +# this turns off the RCS / SCCS implicit rules of GNU Make +% : RCS/%,v +% : RCS/% +% : %,v +% : s.% +% : SCCS/s.% + +# If a rule fails, delete $@. +.DELETE_ON_ERROR: + +HOST_FILE_PROGRAM := file + +UNAME := $(shell uname -m) +ifneq (,$(findstring 86,$(UNAME))) + HOST_ARCH := x86 + ifneq (,$(shell $(HOST_FILE_PROGRAM) -L $(SHELL) | grep 'x86[_-]64')) + HOST_ARCH := x86_64 + endif +else + $(info Unknown host CPU architecture $(UNAME)) + $(error Aborting) +endif + + +ifeq "$(findstring __INTEL_COMPILER, $(shell $(CC) -E -dM -xc /dev/null))" "__INTEL_COMPILER" + ifeq ($(shell test -f /usr/bin/dpkg; echo $$?), 0) + ADDED_INC := -I /usr/include/$(shell dpkg-architecture -qDEB_BUILD_MULTIARCH) + endif +endif + +ARCH := $(HOST_ARCH) +ifeq "$(findstring -m32, $(CXXFLAGS))" "-m32" + ARCH := x86 +endif + +ifeq ($(ARCH), x86) +COMMON_FLAGS += -DITT_ARCH_IA32 +else +COMMON_FLAGS += -DITT_ARCH_IA64 +endif + +CFLAGS += $(COMMON_FLAGS) +CXXFLAGS += $(COMMON_FLAGS) + +# Enable the security flags +COMMON_LDFLAGS := -Wl,-z,relro,-z,now,-z,noexecstack + +# mitigation options +MITIGATION_INDIRECT ?= 0 +MITIGATION_RET ?= 0 +MITIGATION_C ?= 0 +MITIGATION_ASM ?= 0 +MITIGATION_AFTERLOAD ?= 0 +MITIGATION_LIB_PATH := + +ifeq ($(MITIGATION-CVE-2020-0551), LOAD) + MITIGATION_C := 1 + MITIGATION_ASM := 1 + MITIGATION_INDIRECT := 1 + MITIGATION_RET := 1 + MITIGATION_AFTERLOAD := 1 + MITIGATION_LIB_PATH := cve_2020_0551_load +else ifeq ($(MITIGATION-CVE-2020-0551), CF) + MITIGATION_C := 1 + MITIGATION_ASM := 1 + MITIGATION_INDIRECT := 1 + MITIGATION_RET := 1 + MITIGATION_AFTERLOAD := 0 + MITIGATION_LIB_PATH := cve_2020_0551_cf +endif + +MITIGATION_CFLAGS := +MITIGATION_ASFLAGS := +ifeq ($(MITIGATION_C), 1) +ifeq ($(MITIGATION_INDIRECT), 1) + MITIGATION_CFLAGS += -mindirect-branch-register +endif +ifeq ($(MITIGATION_RET), 1) + MITIGATION_CFLAGS += -mfunction-return=thunk-extern +endif +endif + +ifeq ($(MITIGATION_ASM), 1) + MITIGATION_ASFLAGS += -fno-plt +ifeq ($(MITIGATION_AFTERLOAD), 1) + MITIGATION_ASFLAGS += -Wa,-mlfence-after-load=yes +else + MITIGATION_ASFLAGS += -Wa,-mlfence-before-indirect-branch=register +endif +ifeq ($(MITIGATION_RET), 1) + MITIGATION_ASFLAGS += -Wa,-mlfence-before-ret=not +endif +endif + +MITIGATION_CFLAGS += $(MITIGATION_ASFLAGS) + +# Compiler and linker options for an Enclave +# +# We are using '--export-dynamic' so that `g_global_data_sim' etc. +# will be exported to dynamic symbol table. +# +# When `pie' is enabled, the linker (both BFD and Gold) under Ubuntu 14.04 +# will hide all symbols from dynamic symbol table even if they are marked +# as `global' in the LD version script. +ENCLAVE_CFLAGS = -ffreestanding -nostdinc -fvisibility=hidden -fpie -fno-strict-overflow -fno-delete-null-pointer-checks +ENCLAVE_CXXFLAGS = $(ENCLAVE_CFLAGS) -nostdinc++ +ENCLAVE_LDFLAGS = $(COMMON_LDFLAGS) -Wl,-Bstatic -Wl,-Bsymbolic -Wl,--no-undefined \ + -Wl,-pie,-eenclave_entry -Wl,--export-dynamic \ + -Wl,--gc-sections \ + -Wl,--defsym,__ImageBase=0 + +ENCLAVE_CFLAGS += $(MITIGATION_CFLAGS) +ENCLAVE_ASFLAGS = $(MITIGATION_ASFLAGS) \ No newline at end of file diff --git a/tee-worker/rust-sgx-sdk/common/inc/assert.h b/tee-worker/rust-sgx-sdk/common/inc/assert.h new file mode 100644 index 0000000000..a153995416 --- /dev/null +++ b/tee-worker/rust-sgx-sdk/common/inc/assert.h @@ -0,0 +1,63 @@ +/* $OpenBSD: assert.h,v 1.12 2006/01/31 10:53:51 hshoexer Exp $ */ +/* $NetBSD: assert.h,v 1.6 1994/10/26 00:55:44 cgd Exp $ */ + +/*- + * Copyright (c) 1992, 1993 + * The Regents of the University of California. All rights reserved. + * (c) UNIX System Laboratories, Inc. + * All or some portions of this file are derived from material licensed + * to the University of California by American Telephone and Telegraph + * Co. or Unix System Laboratories, Inc. and are reproduced herein with + * the permission of UNIX System Laboratories, Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * @(#)assert.h 8.2 (Berkeley) 1/21/94 + */ + +/* + * Unlike other ANSI header files, may usefully be included + * multiple times, with and without NDEBUG defined. + */ + +#include + +#undef assert + +#ifdef NDEBUG +# define assert(e) ((void)0) +#else +# define assert(e) ((e) ? (void)0 : __assert(__FILE__, __LINE__, __func__, #e)) +#endif + +#ifndef _ASSERT_H_DECLS +#define _ASSERT_H_DECLS +__BEGIN_DECLS + +void _TLIBC_CDECL_ __assert(const char *, int, const char *, const char *); + +__END_DECLS +#endif /* Not _ASSERT_H_DECLS */ + diff --git a/tee-worker/rust-sgx-sdk/common/inc/complex.h b/tee-worker/rust-sgx-sdk/common/inc/complex.h new file mode 100644 index 0000000000..904cb31fbf --- /dev/null +++ b/tee-worker/rust-sgx-sdk/common/inc/complex.h @@ -0,0 +1,134 @@ +/* $OpenBSD: complex.h,v 1.3 2010/07/24 22:17:03 guenther Exp $ */ +/* + * Copyright (c) 2008 Martynas Venckus + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef _COMPLEX_H_ +#define _COMPLEX_H_ + +#include + +/* + * C99 + */ +#ifdef __GNUC__ +#if __STDC_VERSION__ < 199901 +#define _Complex __complex__ +#endif +#define _Complex_I 1.0fi +#elif defined(lint) +#define _Complex_I 1.0fi +#endif + +#define complex _Complex + +/* XXX switch to _Imaginary_I */ +#undef I +#define I _Complex_I + +__BEGIN_DECLS +/* + * Double versions of C99 functions + */ +double complex cacos(double complex); +double complex casin(double complex); +double complex catan(double complex); +double complex ccos(double complex); +double complex csin(double complex); +double complex ctan(double complex); +double complex cacosh(double complex); +double complex casinh(double complex); +double complex catanh(double complex); +double complex ccosh(double complex); +double complex csinh(double complex); +double complex ctanh(double complex); +double complex cexp(double complex); +double complex clog(double complex); +double cabs(double complex); +double complex cpow(double complex, double complex); +double complex csqrt(double complex); +double carg(double complex); +double cimag(double complex); +double complex conj(double complex); +double complex cproj(double complex); +double creal(double complex); +/* + * C99 reserved + */ +double complex clog10(double complex); + +/* + * Float versions of C99 functions + */ +float complex cacosf(float complex); +float complex casinf(float complex); +float complex catanf(float complex); +float complex ccosf(float complex); +float complex csinf(float complex); +float complex ctanf(float complex); +float complex cacoshf(float complex); +float complex casinhf(float complex); +float complex catanhf(float complex); +float complex ccoshf(float complex); +float complex csinhf(float complex); +float complex ctanhf(float complex); +float complex cexpf(float complex); +float complex clogf(float complex); +float cabsf(float complex); +float complex cpowf(float complex, float complex); +float complex csqrtf(float complex); +float cargf(float complex); +float cimagf(float complex); +float complex conjf(float complex); +float complex cprojf(float complex); +float crealf(float complex); +/* + * C99 reserved + */ +float complex clog10f(float complex); + +/* + * Long double versions of C99 functions + */ +long double complex cacosl(long double complex); +long double complex casinl(long double complex); +long double complex catanl(long double complex); +long double complex ccosl(long double complex); +long double complex csinl(long double complex); +long double complex ctanl(long double complex); +long double complex cacoshl(long double complex); +long double complex casinhl(long double complex); +long double complex catanhl(long double complex); +long double complex ccoshl(long double complex); +long double complex csinhl(long double complex); +long double complex ctanhl(long double complex); +long double complex cexpl(long double complex); +long double complex clogl(long double complex); +long double cabsl(long double complex); +long double complex cpowl(long double complex, long double complex); +long double complex csqrtl(long double complex); +long double cargl(long double complex); +long double cimagl(long double complex); +long double complex conjl(long double complex); +long double complex cprojl(long double complex); +long double creall(long double complex); +/* + * C99 reserved + */ +long double complex clog10l(long double complex); + +__END_DECLS + +#endif /* !_COMPLEX_H_ */ diff --git a/tee-worker/rust-sgx-sdk/common/inc/ctype.h b/tee-worker/rust-sgx-sdk/common/inc/ctype.h new file mode 100644 index 0000000000..57ac70ff11 --- /dev/null +++ b/tee-worker/rust-sgx-sdk/common/inc/ctype.h @@ -0,0 +1,65 @@ +/* $OpenBSD: ctype.h,v 1.22 2010/10/01 20:10:24 guenther Exp $ */ +/* $NetBSD: ctype.h,v 1.14 1994/10/26 00:55:47 cgd Exp $ */ + +/* + * Copyright (c) 1989 The Regents of the University of California. + * All rights reserved. + * (c) UNIX System Laboratories, Inc. + * All or some portions of this file are derived from material licensed + * to the University of California by American Telephone and Telegraph + * Co. or Unix System Laboratories, Inc. and are reproduced herein with + * the permission of UNIX System Laboratories, Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * @(#)ctype.h 5.3 (Berkeley) 4/3/91 + */ + +#ifndef _CTYPE_H_ +#define _CTYPE_H_ + +#include + +__BEGIN_DECLS + +int _TLIBC_CDECL_ isalnum(int); +int _TLIBC_CDECL_ isalpha(int); +int _TLIBC_CDECL_ iscntrl(int); +int _TLIBC_CDECL_ isdigit(int); +int _TLIBC_CDECL_ isgraph(int); +int _TLIBC_CDECL_ islower(int); +int _TLIBC_CDECL_ isprint(int); +int _TLIBC_CDECL_ ispunct(int); +int _TLIBC_CDECL_ isspace(int); +int _TLIBC_CDECL_ isupper(int); +int _TLIBC_CDECL_ isxdigit(int); +int _TLIBC_CDECL_ tolower(int); +int _TLIBC_CDECL_ toupper(int); +int _TLIBC_CDECL_ isblank(int); +int _TLIBC_CDECL_ isascii(int); + +__END_DECLS + +#endif /* _CTYPE_H_ */ diff --git a/tee-worker/rust-sgx-sdk/common/inc/dirent.h b/tee-worker/rust-sgx-sdk/common/inc/dirent.h new file mode 100644 index 0000000000..a0ede0375c --- /dev/null +++ b/tee-worker/rust-sgx-sdk/common/inc/dirent.h @@ -0,0 +1,48 @@ +// +// Copyright © 2005-2020 Rich Felker, et al. +// Licensed under the MIT license.s +// + +/* Copyright © 2005-2020 Rich Felker, et al. + +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. */ + +#ifndef _DIRENT_H_ +#define _DIRENT_H_ + +struct dirent { + __ino_t d_ino; + __off_t d_off; + unsigned short d_reclen; + unsigned char d_type; + char d_name[256]; +}; + +struct dirent64 { + __ino64_t d_ino; + __off64_t d_off; + unsigned short d_reclen; + unsigned char d_type; + char d_name[256]; +}; + +#define d_fileno d_ino + +#endif /* _DIRENT_H_ */ diff --git a/tee-worker/rust-sgx-sdk/common/inc/endian.h b/tee-worker/rust-sgx-sdk/common/inc/endian.h new file mode 100644 index 0000000000..2620c5898f --- /dev/null +++ b/tee-worker/rust-sgx-sdk/common/inc/endian.h @@ -0,0 +1,33 @@ +/* $OpenBSD: endian.h,v 1.18 2006/03/27 07:09:24 otto Exp $ */ + +/*- + * Copyright (c) 1997 Niklas Hallqvist. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef _ENDIAN_H_ +#define _ENDIAN_H_ + +#include + +#endif /* _ENDIAN_H_ */ + diff --git a/tee-worker/rust-sgx-sdk/common/inc/errno.h b/tee-worker/rust-sgx-sdk/common/inc/errno.h new file mode 100644 index 0000000000..dbe293cb9e --- /dev/null +++ b/tee-worker/rust-sgx-sdk/common/inc/errno.h @@ -0,0 +1,187 @@ +/* $OpenBSD: errno.h,v 1.1 2005/12/28 16:33:56 millert Exp $ */ + +/* + * Copyright (c) 1982, 1986, 1989, 1993 + * The Regents of the University of California. All rights reserved. + * (c) UNIX System Laboratories, Inc. + * All or some portions of this file are derived from material licensed + * to the University of California by American Telephone and Telegraph + * Co. or Unix System Laboratories, Inc. and are reproduced herein with + * the permission of UNIX System Laboratories, Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * @(#)errno.h 8.5 (Berkeley) 1/21/94 + */ + +#ifndef _ERRNO_H_ +#define _ERRNO_H_ + +#include + +#define EPERM 1 +#define ENOENT 2 +#define ESRCH 3 +#define EINTR 4 +#define EIO 5 +#define ENXIO 6 +#define E2BIG 7 +#define ENOEXEC 8 +#define EBADF 9 +#define ECHILD 10 +#define EAGAIN 11 +#define ENOMEM 12 +#define EACCES 13 +#define EFAULT 14 +#define ENOTBLK 15 +#define EBUSY 16 +#define EEXIST 17 +#define EXDEV 18 +#define ENODEV 19 +#define ENOTDIR 20 +#define EISDIR 21 +#define EINVAL 22 +#define ENFILE 23 +#define EMFILE 24 +#define ENOTTY 25 +#define ETXTBSY 26 +#define EFBIG 27 +#define ENOSPC 28 +#define ESPIPE 29 +#define EROFS 30 +#define EMLINK 31 +#define EPIPE 32 +#define EDOM 33 +#define ERANGE 34 +#define EDEADLK 35 +#define ENAMETOOLONG 36 +#define ENOLCK 37 +#define ENOSYS 38 +#define ENOTEMPTY 39 +#define ELOOP 40 +#define EWOULDBLOCK EAGAIN +#define ENOMSG 42 +#define EIDRM 43 +#define ECHRNG 44 +#define EL2NSYNC 45 +#define EL3HLT 46 +#define EL3RST 47 +#define ELNRNG 48 +#define EUNATCH 49 +#define ENOCSI 50 +#define EL2HLT 51 +#define EBADE 52 +#define EBADR 53 +#define EXFULL 54 +#define ENOANO 55 +#define EBADRQC 56 +#define EBADSLT 57 +#define EDEADLOCK EDEADLK +#define EBFONT 59 +#define ENOSTR 60 +#define ENODATA 61 +#define ETIME 62 +#define ENOSR 63 +#define ENONET 64 +#define ENOPKG 65 +#define EREMOTE 66 +#define ENOLINK 67 +#define EADV 68 +#define ESRMNT 69 +#define ECOMM 70 +#define EPROTO 71 +#define EMULTIHOP 72 +#define EDOTDOT 73 +#define EBADMSG 74 +#define EOVERFLOW 75 +#define ENOTUNIQ 76 +#define EBADFD 77 +#define EREMCHG 78 +#define ELIBACC 79 +#define ELIBBAD 80 +#define ELIBSCN 81 +#define ELIBMAX 82 +#define ELIBEXEC 83 +#define EILSEQ 84 +#define ERESTART 85 +#define ESTRPIPE 86 +#define EUSERS 87 +#define ENOTSOCK 88 +#define EDESTADDRREQ 89 +#define EMSGSIZE 90 +#define EPROTOTYPE 91 +#define ENOPROTOOPT 92 +#define EPROTONOSUPPORT 93 +#define ESOCKTNOSUPPORT 94 +#define EOPNOTSUPP 95 +#define EPFNOSUPPORT 96 +#define EAFNOSUPPORT 97 +#define EADDRINUSE 98 +#define EADDRNOTAVAIL 99 +#define ENETDOWN 100 +#define ENETUNREACH 101 +#define ENETRESET 102 +#define ECONNABORTED 103 +#define ECONNRESET 104 +#define ENOBUFS 105 +#define EISCONN 106 +#define ENOTCONN 107 +#define ESHUTDOWN 108 +#define ETOOMANYREFS 109 +#define ETIMEDOUT 110 +#define ECONNREFUSED 111 +#define EHOSTDOWN 112 +#define EHOSTUNREACH 113 +#define EALREADY 114 +#define EINPROGRESS 115 +#define ESTALE 116 +#define EUCLEAN 117 +#define ENOTNAM 118 +#define ENAVAIL 119 +#define EISNAM 120 +#define EREMOTEIO 121 +#define EDQUOT 122 +#define ENOMEDIUM 123 +#define EMEDIUMTYPE 124 +#define ECANCELED 125 +#define ENOKEY 126 +#define EKEYEXPIRED 127 +#define EKEYREVOKED 128 +#define EKEYREJECTED 129 +#define EOWNERDEAD 130 +#define ENOTRECOVERABLE 131 +#define ERFKILL 132 +#define EHWPOISON 133 +#define ENOTSUP EOPNOTSUPP + +__BEGIN_DECLS + +#ifndef errno +int * _TLIBC_CDECL_ __errno(void); +#define errno (*__errno()) +#endif /* errno */ +__END_DECLS + +#endif /* _ERRNO_H_ */ diff --git a/tee-worker/rust-sgx-sdk/common/inc/float.h b/tee-worker/rust-sgx-sdk/common/inc/float.h new file mode 100644 index 0000000000..e38a7c6a9f --- /dev/null +++ b/tee-worker/rust-sgx-sdk/common/inc/float.h @@ -0,0 +1,84 @@ +/* $OpenBSD: float.h,v 1.3 2008/07/21 20:50:54 martynas Exp $ */ +/* $NetBSD: float.h,v 1.8 1995/06/20 20:45:37 jtc Exp $ */ + +/* + * Copyright (c) 1989 Regents of the University of California. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * @(#)float.h 7.1 (Berkeley) 5/8/90 + */ + +#ifndef _FLOAT_H_ +#define _FLOAT_H_ + +#include + +#define FLT_RADIX 2 /* b */ + +// The rounding direction can be specified by fesetround() in +#define FLT_ROUNDS 1 /* addition rounding: near */ +#define DECIMAL_DIG 21 /* max precision in decimal digits */ + +// NOTE: FLT_EVAL_METHOD is -1 under FREEBSD x86. +#ifdef __i386__ +#define FLT_EVAL_METHOD 2 /* long double */ +#else +#define FLT_EVAL_METHOD 0 /* no promotions */ +#endif + +#define DBL_MANT_DIG 53 +#define DBL_EPSILON 2.2204460492503131E-16 +#define DBL_DIG 15 +#define DBL_MIN_EXP (-1021) +#define DBL_MIN 2.2250738585072014E-308 +#define DBL_MIN_10_EXP (-307) +#define DBL_MAX_EXP 1024 +#define DBL_MAX_10_EXP 308 + +#define FLT_MANT_DIG 24 /* p */ +#define FLT_DIG 6 /* floor((p-1)*log10(b))+(b == 10) */ +#define FLT_MIN_EXP (-125) /* emin */ +#define FLT_MIN_10_EXP (-37) /* ceil(log10(b**(emin-1))) */ +#define FLT_MAX_EXP 128 /* emax */ +#define FLT_MAX_10_EXP 38 /* floor(log10((1-b**(-p))*b**emax)) */ + +#define DBL_MAX 1.7976931348623157E+308 +#define FLT_EPSILON 1.19209290E-07F /* b**(1-p) */ +#define FLT_MIN 1.17549435E-38F /* b**(emin-1) */ +#define FLT_MAX 3.40282347E+38F /* (1-b**(-p))*b**emax */ + +#define LDBL_MANT_DIG 64 +#define LDBL_EPSILON 1.08420217248550443401e-19L +#define LDBL_DIG 18 +#define LDBL_MIN_EXP (-16381) +#define LDBL_MIN 3.36210314311209350626e-4932L +#define LDBL_MIN_10_EXP (-4931) +#define LDBL_MAX_EXP 16384 +#define LDBL_MAX 1.18973149535723176502e+4932L +#define LDBL_MAX_10_EXP 4932 + +#endif /* _FLOAT_H_ */ diff --git a/tee-worker/rust-sgx-sdk/common/inc/inttypes.h b/tee-worker/rust-sgx-sdk/common/inc/inttypes.h new file mode 100644 index 0000000000..fbc009c975 --- /dev/null +++ b/tee-worker/rust-sgx-sdk/common/inc/inttypes.h @@ -0,0 +1,330 @@ +/* $OpenBSD: inttypes.h,v 1.10 2009/01/13 18:13:51 kettenis Exp $ */ + +/* + * Copyright (c) 1997, 2005 Todd C. Miller + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef _INTTYPES_H_ +#define _INTTYPES_H_ + +#include + +/* + * 7.8.1 Macros for format specifiers + * + * Each of the following object-like macros expands to a string + * literal containing a conversion specifier, possibly modified by + * a prefix such as hh, h, l, or ll, suitable for use within the + * format argument of a formatted input/output function when + * converting the corresponding integer type. These macro names + * have the general form of PRI (character string literals for the + * fprintf family) or SCN (character string literals for the fscanf + * family), followed by the conversion specifier, followed by a + * name corresponding to a similar typedef name. For example, + * PRIdFAST32 can be used in a format string to print the value of + * an integer of type int_fast32_t. + */ + +/* fprintf macros for signed integers */ +#define PRId8 "d" /* int8_t */ +#define PRId16 "d" /* int16_t */ +#define PRId32 "d" /* int32_t */ +#ifdef __x86_64__ +#define PRId64 "ld" /* int64_t */ +#else +#define PRId64 "lld" /* int64_t */ +#endif + +#define PRIdLEAST8 "d" /* int_least8_t */ +#define PRIdLEAST16 "d" /* int_least16_t */ +#define PRIdLEAST32 "d" /* int_least32_t */ +#ifdef __x86_64__ +#define PRIdLEAST64 "ld" /* int_least64_t */ +#else +#define PRIdLEAST64 "lld" /* int_least64_t */ +#endif + +#define PRIdFAST8 "d" /* int_fast8_t */ +#ifdef __x86_64__ +#define PRIdFAST16 "ld" /* int_fast16_t */ +#define PRIdFAST32 "ld" /* int_fast32_t */ +#define PRIdFAST64 "ld" /* int_fast64_t */ +#else +#define PRIdFAST16 "d" /* int_fast16_t */ +#define PRIdFAST32 "d" /* int_fast32_t */ +#define PRIdFAST64 "lld" /* int_fast64_t */ +#endif + +#ifdef __x86_64__ +#define PRIdMAX "ld" /* intmax_t */ +#else +#if defined(__i386__) +#define PRIdMAX "lld" /* intmax_t */ +#else +#define PRIdMAX "jd" /* intmax_t */ +#endif +#endif + +#ifdef __i386__ +#define PRIdPTR "d" /* intptr_t */ +#else +#define PRIdPTR "ld" /* intptr_t */ +#endif + +#define PRIi8 "i" /* int8_t */ +#define PRIi16 "i" /* int16_t */ +#define PRIi32 "i" /* int32_t */ +#ifdef __x86_64__ +#define PRIi64 "li" /* int64_t */ +#else +#define PRIi64 "lli" /* int64_t */ +#endif + +#define PRIiLEAST8 "i" /* int_least8_t */ +#define PRIiLEAST16 "i" /* int_least16_t */ +#define PRIiLEAST32 "i" /* int_least32_t */ +#ifdef __x86_64__ +#define PRIiLEAST64 "li" /* int_least64_t */ +#else +#define PRIiLEAST64 "lli" /* int_least64_t */ +#endif + +#define PRIiFAST8 "i" /* int_fast8_t */ +#ifdef __x86_64__ +#define PRIiFAST16 "li" /* int_fast16_t */ +#define PRIiFAST32 "li" /* int_fast32_t */ +#define PRIiFAST64 "li" /* int_fast64_t */ +#else +#define PRIiFAST16 "i" /* int_fast16_t */ +#define PRIiFAST32 "i" /* int_fast32_t */ +#define PRIiFAST64 "lli" /* int_fast64_t */ +#endif + +#ifdef __x86_64__ +#define PRIiMAX "li" /* intmax_t */ +#else +#if defined(__i386__) +#define PRIiMAX "lli" /* intmax_t */ +#else +#define PRIiMAX "ji" /* intmax_t */ +#endif +#endif + +#ifdef __i386__ +#define PRIiPTR "i" /* intptr_t */ +#else +#define PRIiPTR "li" /* intptr_t */ +#endif + +/* fprintf macros for unsigned integers */ +#define PRIo8 "o" /* int8_t */ +#define PRIo16 "o" /* int16_t */ +#define PRIo32 "o" /* int32_t */ +#ifdef __x86_64__ +#define PRIo64 "lo" /* int64_t */ +#else +#define PRIo64 "llo" /* int64_t */ +#endif + +#define PRIoLEAST8 "o" /* int_least8_t */ +#define PRIoLEAST16 "o" /* int_least16_t */ +#define PRIoLEAST32 "o" /* int_least32_t */ +#ifdef __x86_64__ +#define PRIoLEAST64 "lo" /* int_least64_t */ +#else +#define PRIoLEAST64 "llo" /* int_least64_t */ +#endif + +#define PRIoFAST8 "o" /* int_fast8_t */ +#ifdef __x86_64__ +#define PRIoFAST16 "lo" /* int_fast16_t */ +#define PRIoFAST32 "lo" /* int_fast32_t */ +#define PRIoFAST64 "lo" /* int_fast64_t */ +#else +#define PRIoFAST16 "o" /* int_fast16_t */ +#define PRIoFAST32 "o" /* int_fast32_t */ +#define PRIoFAST64 "llo" /* int_fast64_t */ +#endif + +#ifdef __x86_64__ +#define PRIoMAX "lo" /* intmax_t */ +#else +#if defined(__i386__) +#define PRIoMAX "llo" /* intmax_t */ +#else +#define PRIoMAX "jo" /* intmax_t */ +#endif +#endif + +#ifdef __i386__ +#define PRIoPTR "o" /* intptr_t */ +#else +#define PRIoPTR "lo" /* intptr_t */ +#endif + +#define PRIu8 "u" /* uint8_t */ +#define PRIu16 "u" /* uint16_t */ +#define PRIu32 "u" /* uint32_t */ + +#ifdef __x86_64__ +#define PRIu64 "lu" /* uint64_t */ +#else +#define PRIu64 "llu" /* uint64_t */ +#endif + +#define PRIuLEAST8 "u" /* uint_least8_t */ +#define PRIuLEAST16 "u" /* uint_least16_t */ +#define PRIuLEAST32 "u" /* uint_least32_t */ + +#ifdef __x86_64__ +#define PRIuLEAST64 "lu" /* uint_least64_t */ +#else +#define PRIuLEAST64 "llu" /* uint_least64_t */ +#endif + +#define PRIuFAST8 "u" /* uint_fast8_t */ + +#ifdef __x86_64__ +#define PRIuFAST16 "lu" /* uint_fast16_t */ +#define PRIuFAST32 "lu" /* uint_fast32_t */ +#define PRIuFAST64 "lu" /* uint_fast64_t */ +#else +#define PRIuFAST16 "u" /* uint_fast16_t */ +#define PRIuFAST32 "u" /* uint_fast32_t */ +#define PRIuFAST64 "llu" /* uint_fast64_t */ +#endif + +#ifdef __x86_64__ +#define PRIuMAX "lu" /* uintmax_t */ +#else +#if defined(__i386__) +#define PRIuMAX "llu" /* uintmax_t */ +#else +#define PRIuMAX "ju" /* uintmax_t */ +#endif +#endif + +#ifdef __i386__ +#define PRIuPTR "u" /* uintptr_t */ +#else +#define PRIuPTR "lu" /* uintptr_t */ +#endif + +#define PRIx8 "x" /* uint8_t */ +#define PRIx16 "x" /* uint16_t */ +#define PRIx32 "x" /* uint32_t */ +#ifdef __x86_64__ +#define PRIx64 "lx" /* uint64_t */ +#else +#define PRIx64 "llx" /* uint64_t */ +#endif + +#define PRIxLEAST8 "x" /* uint_least8_t */ +#define PRIxLEAST16 "x" /* uint_least16_t */ +#define PRIxLEAST32 "x" /* uint_least32_t */ +#ifdef __x86_64__ +#define PRIxLEAST64 "lx" /* uint_least64_t */ +#else +#define PRIxLEAST64 "llx" /* uint_least64_t */ +#endif + +#define PRIxFAST8 "x" /* uint_fast8_t */ +#ifdef __x86_64__ +#define PRIxFAST16 "lx" /* uint_fast16_t */ +#define PRIxFAST32 "lx" /* uint_fast32_t */ +#define PRIxFAST64 "lx" /* uint_fast64_t */ +#else +#define PRIxFAST16 "x" /* uint_fast16_t */ +#define PRIxFAST32 "x" /* uint_fast32_t */ +#define PRIxFAST64 "llx" /* uint_fast64_t */ +#endif + +#ifdef __x86_64__ +#define PRIxMAX "lx" /* uintmax_t */ +#else +#if defined(__i386__) +#define PRIxMAX "llx" /* uintmax_t */ +#else +#define PRIxMAX "jx" /* uintmax_t */ +#endif +#endif + +#ifdef __i386__ +#define PRIxPTR "x" /* uintptr_t */ +#else +#define PRIxPTR "lx" /* uintptr_t */ +#endif + +#define PRIX8 "X" /* uint8_t */ +#define PRIX16 "X" /* uint16_t */ +#define PRIX32 "X" /* uint32_t */ + +#ifdef __x86_64__ +#define PRIX64 "lX" /* uint64_t */ +#else +#define PRIX64 "llX" /* uint64_t */ +#endif + +#define PRIXLEAST8 "X" /* uint_least8_t */ +#define PRIXLEAST16 "X" /* uint_least16_t */ +#define PRIXLEAST32 "X" /* uint_least32_t */ +#ifdef __x86_64__ +#define PRIXLEAST64 "lX" /* uint_least64_t */ +#else +#define PRIXLEAST64 "llX" /* uint_least64_t */ +#endif + +#define PRIXFAST8 "X" /* uint_fast8_t */ +#ifdef __x86_64__ +#define PRIXFAST16 "lX" /* uint_fast16_t */ +#define PRIXFAST32 "lX" /* uint_fast32_t */ +#define PRIXFAST64 "lX" /* uint_fast64_t */ +#else +#define PRIXFAST16 "X" /* uint_fast16_t */ +#define PRIXFAST32 "X" /* uint_fast32_t */ +#define PRIXFAST64 "llX" /* uint_fast64_t */ +#endif + +#ifdef __x86_64__ +#define PRIXMAX "lX" /* uintmax_t */ +#else +#if defined(__i386__) +#define PRIXMAX "llX" /* uintmax_t */ +#else +#define PRIXMAX "jX" /* uintmax_t */ +#endif +#endif + +#ifdef __i386__ +#define PRIXPTR "X" /* uintptr_t */ +#else +#define PRIXPTR "lX" /* uintptr_t */ +#endif + +typedef struct { + intmax_t quot; /* quotient */ + intmax_t rem; /* remainder */ +} imaxdiv_t; + +__BEGIN_DECLS + +intmax_t _TLIBC_CDECL_ imaxabs(intmax_t); +imaxdiv_t _TLIBC_CDECL_ imaxdiv(intmax_t, intmax_t); +intmax_t _TLIBC_CDECL_ strtoimax(const char *, char **, int); +uintmax_t _TLIBC_CDECL_ strtoumax(const char *, char **, int); + +__END_DECLS + +#endif /* _INTTYPES_H_ */ diff --git a/tee-worker/rust-sgx-sdk/common/inc/iso646.h b/tee-worker/rust-sgx-sdk/common/inc/iso646.h new file mode 100644 index 0000000000..a0c341b658 --- /dev/null +++ b/tee-worker/rust-sgx-sdk/common/inc/iso646.h @@ -0,0 +1,26 @@ +/* $OpenBSD: iso646.h,v 1.3 2001/10/11 00:05:21 espie Exp $ */ +/* $NetBSD: iso646.h,v 1.1 1995/02/17 09:08:10 jtc Exp $ */ + +/* + * Written by J.T. Conklin 02/16/95. + * Public domain. + */ + +#ifndef _ISO646_H_ +#define _ISO646_H_ + +#ifndef __cplusplus +#define and && +#define and_eq &= +#define bitand & +#define bitor | +#define compl ~ +#define not ! +#define not_eq != +#define or || +#define or_eq |= +#define xor ^ +#define xor_eq ^= +#endif + +#endif /* !_ISO646_H_ */ diff --git a/tee-worker/rust-sgx-sdk/common/inc/limits.h b/tee-worker/rust-sgx-sdk/common/inc/limits.h new file mode 100644 index 0000000000..9d42cb545c --- /dev/null +++ b/tee-worker/rust-sgx-sdk/common/inc/limits.h @@ -0,0 +1,41 @@ +/* $OpenBSD: limits.h,v 1.15 2008/02/10 09:59:54 kettenis Exp $ */ +/* $NetBSD: limits.h,v 1.7 1994/10/26 00:56:00 cgd Exp $ */ + +/* + * Copyright (c) 1988 The Regents of the University of California. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * @(#)limits.h 5.9 (Berkeley) 4/3/91 + */ + + +#ifndef _LIMITS_H_ +#define _LIMITS_H_ + +#include + +#endif /* !_LIMITS_H_ */ diff --git a/tee-worker/rust-sgx-sdk/common/inc/math.h b/tee-worker/rust-sgx-sdk/common/inc/math.h new file mode 100644 index 0000000000..6ea425b840 --- /dev/null +++ b/tee-worker/rust-sgx-sdk/common/inc/math.h @@ -0,0 +1,430 @@ +/* $OpenBSD: math.h,v 1.27 2010/12/14 11:16:15 martynas Exp $ */ +/* + * ==================================================== + * Copyright (C) 1993 by Sun Microsystems, Inc. All rights reserved. + * + * Developed at SunPro, a Sun Microsystems, Inc. business. + * Permission to use, copy, modify, and distribute this + * software is freely granted, provided that this notice + * is preserved. + * ==================================================== + */ + +/* + * from: @(#)fdlibm.h 5.1 93/09/24 + */ + +#ifndef _MATH_H_ +#define _MATH_H_ + +#include +#include +#include + +#include + +typedef __float_t float_t; +typedef __double_t double_t; + +#define FP_NAN 0x00 +#define FP_INFINITE 0x01 +#define FP_ZERO 0x02 +#define FP_SUBNORMAL 0x03 +#define FP_NORMAL 0x04 + +#define FP_ILOGB0 (-INT_MAX - 1) +#define FP_ILOGBNAN (-INT_MAX - 1) + +#define fpclassify(x) \ + ((sizeof (x) == sizeof (float)) ? \ + __fpclassifyf(x) \ + : (sizeof (x) == sizeof (double)) ? \ + __fpclassify(x) \ + : __fpclassifyl(x)) +#define isfinite(x) \ + ((sizeof (x) == sizeof (float)) ? \ + __isfinitef(x) \ + : (sizeof (x) == sizeof (double)) ? \ + __isfinite(x) \ + : __isfinitel(x)) +#define isnormal(x) \ + ((sizeof (x) == sizeof (float)) ? \ + __isnormalf(x) \ + : (sizeof (x) == sizeof (double)) ? \ + __isnormal(x) \ + : __isnormall(x)) +#define signbit(x) \ + ((sizeof (x) == sizeof (float)) ? \ + __signbitf(x) \ + : (sizeof (x) == sizeof (double)) ? \ + __signbit(x) \ + : __signbitl(x)) +#define isinf(x) \ + ((sizeof (x) == sizeof (float)) ? \ + __isinff(x) \ + : (sizeof (x) == sizeof (double)) ? \ + __isinf(x) \ + : __isinfl(x)) +#define isnan(x) \ + ((sizeof (x) == sizeof (float)) ? \ + __isnanf(x) \ + : (sizeof (x) == sizeof (double)) ? \ + __isnan(x) \ + : __isnanl(x)) + +#define isgreater(x, y) (!isunordered((x), (y)) && (x) > (y)) +#define isgreaterequal(x, y) (!isunordered((x), (y)) && (x) >= (y)) +#define isless(x, y) (!isunordered((x), (y)) && (x) < (y)) +#define islessequal(x, y) (!isunordered((x), (y)) && (x) <= (y)) +#define islessgreater(x, y) (!isunordered((x), (y)) && ((x) > (y) || (y) > (x))) +#define isunordered(x, y) (isnan(x) || isnan(y)) + +__BEGIN_DECLS + +extern char __infinity[]; +#define HUGE_VAL (*(double *)(void *)__infinity) +#define HUGE_VALF ((float)HUGE_VAL) +#define HUGE_VALL ((long double)HUGE_VAL) +#define INFINITY HUGE_VALF +extern char __nan[]; +#define NAN (*(float *)(void *)__nan) + +/* + * ANSI/POSIX + */ +double _TLIBC_CDECL_ acos(double); +double _TLIBC_CDECL_ asin(double); +double _TLIBC_CDECL_ atan(double); +double _TLIBC_CDECL_ atan2(double, double); +double _TLIBC_CDECL_ cos(double); +double _TLIBC_CDECL_ sin(double); +double _TLIBC_CDECL_ tan(double); + +double _TLIBC_CDECL_ cosh(double); +double _TLIBC_CDECL_ sinh(double); +double _TLIBC_CDECL_ tanh(double); + +double _TLIBC_CDECL_ exp(double); +double _TLIBC_CDECL_ frexp(double, int *); +double _TLIBC_CDECL_ ldexp(double, int); +double _TLIBC_CDECL_ log(double); +double _TLIBC_CDECL_ log10(double); +double _TLIBC_CDECL_ modf(double, double *); + +double _TLIBC_CDECL_ pow(double, double); +double _TLIBC_CDECL_ sqrt(double); + +double _TLIBC_CDECL_ ceil(double); +double _TLIBC_CDECL_ fabs(double); +double _TLIBC_CDECL_ floor(double); +double _TLIBC_CDECL_ fmod(double, double); + +/* + * C99 + */ +double _TLIBC_CDECL_ acosh(double); +double _TLIBC_CDECL_ asinh(double); +double _TLIBC_CDECL_ atanh(double); + +double _TLIBC_CDECL_ exp2(double); +double _TLIBC_CDECL_ expm1(double); +int _TLIBC_CDECL_ ilogb(double); +double _TLIBC_CDECL_ log1p(double); +double _TLIBC_CDECL_ log2(double); +double _TLIBC_CDECL_ logb(double); +double _TLIBC_CDECL_ scalbn(double, int); +double _TLIBC_CDECL_ scalbln(double, long int); + +double _TLIBC_CDECL_ cbrt(double); +double _TLIBC_CDECL_ hypot(double, double); + +double _TLIBC_CDECL_ erf(double); +double _TLIBC_CDECL_ erfc(double); +double _TLIBC_CDECL_ lgamma(double); +double _TLIBC_CDECL_ tgamma(double); + +double _TLIBC_CDECL_ nearbyint(double); +double _TLIBC_CDECL_ rint(double); +long int _TLIBC_CDECL_ lrint(double); +long long int _TLIBC_CDECL_ llrint(double); +double _TLIBC_CDECL_ round(double); +long int _TLIBC_CDECL_ lround(double); +long long int _TLIBC_CDECL_ llround(double); +double _TLIBC_CDECL_ trunc(double); + +double _TLIBC_CDECL_ remainder(double, double); +double _TLIBC_CDECL_ remquo(double, double, int *); + +double _TLIBC_CDECL_ copysign(double, double); +double _TLIBC_CDECL_ nan(const char *); +double _TLIBC_CDECL_ nextafter(double, double); + +double _TLIBC_CDECL_ fdim(double, double); +double _TLIBC_CDECL_ fmax(double, double); +double _TLIBC_CDECL_ fmin(double, double); + +double _TLIBC_CDECL_ fma(double, double, double); + +/* + * Float versions of C99 functions + */ + +float _TLIBC_CDECL_ acosf(float); +float _TLIBC_CDECL_ asinf(float); +float _TLIBC_CDECL_ atanf(float); +float _TLIBC_CDECL_ atan2f(float, float); +float _TLIBC_CDECL_ cosf(float); +float _TLIBC_CDECL_ sinf(float); +float _TLIBC_CDECL_ tanf(float); + +float _TLIBC_CDECL_ acoshf(float); +float _TLIBC_CDECL_ asinhf(float); +float _TLIBC_CDECL_ atanhf(float); +float _TLIBC_CDECL_ coshf(float); +float _TLIBC_CDECL_ sinhf(float); +float _TLIBC_CDECL_ tanhf(float); + +float _TLIBC_CDECL_ expf(float); +float _TLIBC_CDECL_ exp2f(float); +float _TLIBC_CDECL_ expm1f(float); +float _TLIBC_CDECL_ frexpf(float, int *); +int _TLIBC_CDECL_ ilogbf(float); +float _TLIBC_CDECL_ ldexpf(float, int); +float _TLIBC_CDECL_ logf(float); +float _TLIBC_CDECL_ log10f(float); +float _TLIBC_CDECL_ log1pf(float); +float _TLIBC_CDECL_ log2f(float); +float _TLIBC_CDECL_ logbf(float); +float _TLIBC_CDECL_ modff(float, float *); +float _TLIBC_CDECL_ scalbnf(float, int); +float _TLIBC_CDECL_ scalblnf(float, long int); + +float _TLIBC_CDECL_ cbrtf(float); +float _TLIBC_CDECL_ fabsf(float); +float _TLIBC_CDECL_ hypotf(float, float); +float _TLIBC_CDECL_ powf(float, float); +float _TLIBC_CDECL_ sqrtf(float); + +float _TLIBC_CDECL_ erff(float); +float _TLIBC_CDECL_ erfcf(float); +float _TLIBC_CDECL_ lgammaf(float); +float _TLIBC_CDECL_ tgammaf(float); + +float _TLIBC_CDECL_ ceilf(float); +float _TLIBC_CDECL_ floorf(float); +float _TLIBC_CDECL_ nearbyintf(float); + +float _TLIBC_CDECL_ rintf(float); +long int _TLIBC_CDECL_ lrintf(float); +long long int _TLIBC_CDECL_ llrintf(float); +float _TLIBC_CDECL_ roundf(float); +long int _TLIBC_CDECL_ lroundf(float); +long long int _TLIBC_CDECL_ llroundf(float); +float _TLIBC_CDECL_ truncf(float); + +float _TLIBC_CDECL_ fmodf(float, float); +float _TLIBC_CDECL_ remainderf(float, float); +float _TLIBC_CDECL_ remquof(float, float, int *); + +float _TLIBC_CDECL_ copysignf(float, float); +float _TLIBC_CDECL_ nanf(const char *); +float _TLIBC_CDECL_ nextafterf(float, float); + +float _TLIBC_CDECL_ fdimf(float, float); +float _TLIBC_CDECL_ fmaxf(float, float); +float _TLIBC_CDECL_ fminf(float, float); + +float _TLIBC_CDECL_ fmaf(float, float, float); + +/* + * Long double versions of C99 functions + */ + +/* Macros defining long double functions to be their double counterparts + * (long double is synonymous with double in this implementation). + */ + +long double _TLIBC_CDECL_ acosl(long double); +long double _TLIBC_CDECL_ asinl(long double); +long double _TLIBC_CDECL_ atanl(long double); +long double _TLIBC_CDECL_ atan2l(long double, long double); +long double _TLIBC_CDECL_ cosl(long double); +long double _TLIBC_CDECL_ sinl(long double); +long double _TLIBC_CDECL_ tanl(long double); + +long double _TLIBC_CDECL_ acoshl(long double); +long double _TLIBC_CDECL_ asinhl(long double); +long double _TLIBC_CDECL_ atanhl(long double); +long double _TLIBC_CDECL_ coshl(long double); +long double _TLIBC_CDECL_ sinhl(long double); +long double _TLIBC_CDECL_ tanhl(long double); + +long double _TLIBC_CDECL_ expl(long double); +long double _TLIBC_CDECL_ exp2l(long double); +long double _TLIBC_CDECL_ expm1l(long double); +long double _TLIBC_CDECL_ frexpl(long double, int *); +int _TLIBC_CDECL_ ilogbl(long double); +long double _TLIBC_CDECL_ ldexpl(long double, int); +long double _TLIBC_CDECL_ logl(long double); +long double _TLIBC_CDECL_ log10l(long double); +long double _TLIBC_CDECL_ log1pl(long double); +long double _TLIBC_CDECL_ log2l(long double); +long double _TLIBC_CDECL_ logbl(long double); +long double _TLIBC_CDECL_ modfl(long double, long double *); +long double _TLIBC_CDECL_ scalbnl(long double, int); +long double _TLIBC_CDECL_ scalblnl(long double, long int); + +long double _TLIBC_CDECL_ cbrtl(long double); +long double _TLIBC_CDECL_ fabsl(long double); +long double _TLIBC_CDECL_ hypotl(long double, long double); +long double _TLIBC_CDECL_ powl(long double, long double); +long double _TLIBC_CDECL_ sqrtl(long double); + +long double _TLIBC_CDECL_ erfl(long double); +long double _TLIBC_CDECL_ erfcl(long double); +long double _TLIBC_CDECL_ lgammal(long double); +long double _TLIBC_CDECL_ tgammal(long double); + +long double _TLIBC_CDECL_ ceill(long double); +long double _TLIBC_CDECL_ floorl(long double); +long double _TLIBC_CDECL_ nearbyintl(long double); +long double _TLIBC_CDECL_ rintl(long double); +long int _TLIBC_CDECL_ lrintl(long double); +long long int _TLIBC_CDECL_ llrintl(long double); +long double _TLIBC_CDECL_ roundl(long double); +long int _TLIBC_CDECL_ lroundl(long double); +long long int _TLIBC_CDECL_ llroundl(long double); +long double _TLIBC_CDECL_ truncl(long double); + +long double _TLIBC_CDECL_ fmodl(long double, long double); +long double _TLIBC_CDECL_ remainderl(long double, long double); +long double _TLIBC_CDECL_ remquol(long double, long double, int *); + +long double _TLIBC_CDECL_ copysignl(long double, long double); +long double _TLIBC_CDECL_ nanl(const char *); +long double _TLIBC_CDECL_ nextafterl(long double, long double); + +long double _TLIBC_CDECL_ fdiml(long double, long double); +long double _TLIBC_CDECL_ fmaxl(long double, long double); +long double _TLIBC_CDECL_ fminl(long double, long double); +long double _TLIBC_CDECL_ fmal(long double, long double, long double); + +/* nexttoward(): +* The implementation in Intel math library is incompatible with MSVC. +* Because sizeof(long double) is 8bytes with MSVC, +* but the expected long double size is 10bytes. +* And by default, MSVC doesn't provide nexttoward(). +* So we only provide Linux version here. +*/ +double _TLIBC_CDECL_ nexttoward(double, long double); +float _TLIBC_CDECL_ nexttowardf(float, long double); + +long double _TLIBC_CDECL_ nexttowardl(long double, long double); + +/* + * Library implementation + */ +int _TLIBC_CDECL_ __fpclassify(double); +int _TLIBC_CDECL_ __fpclassifyf(float); +int _TLIBC_CDECL_ __isfinite(double); +int _TLIBC_CDECL_ __isfinitef(float); +int _TLIBC_CDECL_ __isinf(double); +int _TLIBC_CDECL_ __isinff(float); +int _TLIBC_CDECL_ __isnan(double); +int _TLIBC_CDECL_ __isnanf(float); +int _TLIBC_CDECL_ __isnormal(double); +int _TLIBC_CDECL_ __isnormalf(float); +int _TLIBC_CDECL_ __signbit(double); +int _TLIBC_CDECL_ __signbitf(float); + +int _TLIBC_CDECL_ __fpclassifyl(long double); +int _TLIBC_CDECL_ __isfinitel(long double); +int _TLIBC_CDECL_ __isinfl(long double); +int _TLIBC_CDECL_ __isnanl(long double); +int _TLIBC_CDECL_ __isnormall(long double); +int _TLIBC_CDECL_ __signbitl(long double); + +/* + * Non-C99 functions. + */ +double _TLIBC_CDECL_ drem(double, double); +double _TLIBC_CDECL_ exp10(double); +double _TLIBC_CDECL_ gamma(double); +double _TLIBC_CDECL_ gamma_r(double, int *); +double _TLIBC_CDECL_ j0(double); +double _TLIBC_CDECL_ j1(double); +double _TLIBC_CDECL_ jn(int, double); +double _TLIBC_CDECL_ lgamma_r(double, int *); +double _TLIBC_CDECL_ pow10(double); +double _TLIBC_CDECL_ scalb(double, double); +/* C99 Macro signbit.*/ +double _TLIBC_CDECL_ significand(double); +void _TLIBC_CDECL_ sincos(double, double *, double *); +double _TLIBC_CDECL_ y0(double); +double _TLIBC_CDECL_ y1(double); +double _TLIBC_CDECL_ yn(int, double); +/* C99 Macro isinf.*/ +/* C99 Macro isnan.*/ +int _TLIBC_CDECL_ finite(double); + +float _TLIBC_CDECL_ dremf(float, float); +float _TLIBC_CDECL_ exp10f(float); +float _TLIBC_CDECL_ gammaf(float); +float _TLIBC_CDECL_ gammaf_r(float, int *); +float _TLIBC_CDECL_ j0f(float); +float _TLIBC_CDECL_ j1f(float); +float _TLIBC_CDECL_ jnf(int, float); +float _TLIBC_CDECL_ lgammaf_r(float, int *); +float _TLIBC_CDECL_ pow10f(float); +float _TLIBC_CDECL_ scalbf(float, float); +int _TLIBC_CDECL_ signbitf(float); +float _TLIBC_CDECL_ significandf(float); +void _TLIBC_CDECL_ sincosf(float, float *, float *); +float _TLIBC_CDECL_ y0f(float); +float _TLIBC_CDECL_ y1f(float); +float _TLIBC_CDECL_ ynf(int, float); +int _TLIBC_CDECL_ finitef(float); +int _TLIBC_CDECL_ isinff(float); +int _TLIBC_CDECL_ isnanf(float); + +long double _TLIBC_CDECL_ dreml(long double, long double); +long double _TLIBC_CDECL_ exp10l(long double); +long double _TLIBC_CDECL_ gammal(long double); +long double _TLIBC_CDECL_ gammal_r(long double, int *); +long double _TLIBC_CDECL_ j0l(long double); +long double _TLIBC_CDECL_ j1l(long double); +long double _TLIBC_CDECL_ jnl(int, long double); +long double _TLIBC_CDECL_ lgammal_r(long double, int *); +long double _TLIBC_CDECL_ pow10l(long double); +long double _TLIBC_CDECL_ scalbl(long double, long double); +int _TLIBC_CDECL_ signbitl(long double); +long double _TLIBC_CDECL_ significandl(long double); +void _TLIBC_CDECL_ sincosl(long double, long double *, long double *); +long double _TLIBC_CDECL_ y1l(long double); +long double _TLIBC_CDECL_ y0l(long double); +long double _TLIBC_CDECL_ ynl(int, long double); +int _TLIBC_CDECL_ finitel(long double); +int _TLIBC_CDECL_ isinfl(long double); +int _TLIBC_CDECL_ isnanl(long double); + +/* + * TODO: From Intel Decimal Floating-Point Math Library + * signbitd32/signbitd64/signbitd128, finited32/finited64/finited128 + * isinfd32/isinfd64/isinfd128, isnand32/isnand64/isnand128 + */ +#if defined(__cplusplus) +/* Clang does not support decimal floating point types. + * + * c.f.: + * http://clang.llvm.org/docs/UsersManual.html#gcc-extensions-not-implemented-yet + */ +#if !defined(__clang__) +typedef float _Decimal32 __attribute__((mode(SD))); +typedef float _Decimal64 __attribute__((mode(DD))); +typedef float _Decimal128 __attribute__((mode(TD))); +#endif +#endif + +__END_DECLS + +#endif /* !_MATH_H_ */ diff --git a/tee-worker/rust-sgx-sdk/common/inc/mbusafecrt.h b/tee-worker/rust-sgx-sdk/common/inc/mbusafecrt.h new file mode 100644 index 0000000000..91d888b3f8 --- /dev/null +++ b/tee-worker/rust-sgx-sdk/common/inc/mbusafecrt.h @@ -0,0 +1,85 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// + +/*** +* mbusafecrt.h - public declarations for SafeCRT lib +* + +* +* Purpose: +* This file contains the public declarations SafeCRT +* functions ported to MacOS. These are the safe versions of +* functions standard functions banned by SWI +* + +****/ + +/* shields! */ + +#ifndef MBUSAFECRT_H +#define MBUSAFECRT_H +#include +#include +#include +typedef wchar_t WCHAR; + +#ifdef __cplusplus + extern "C" { +#endif + +extern errno_t strcat_s( char* ioDest, size_t inDestBufferSize, const char* inSrc ); +extern errno_t wcscat_s( WCHAR* ioDest, size_t inDestBufferSize, const WCHAR* inSrc ); + +extern errno_t strncat_s( char* ioDest, size_t inDestBufferSize, const char* inSrc, size_t inCount ); +extern errno_t wcsncat_s( WCHAR* ioDest, size_t inDestBufferSize, const WCHAR* inSrc, size_t inCount ); + +extern errno_t strcpy_s( char* outDest, size_t inDestBufferSize, const char* inSrc ); +extern errno_t wcscpy_s( WCHAR* outDest, size_t inDestBufferSize, const WCHAR* inSrc ); + +extern errno_t strncpy_s( char* outDest, size_t inDestBufferSize, const char* inSrc, size_t inCount ); +extern errno_t wcsncpy_s( WCHAR* outDest, size_t inDestBufferSize, const WCHAR* inSrc, size_t inCount ); + +extern char* strtok_s( char* inString, const char* inControl, char** ioContext ); +extern WCHAR* wcstok_s( WCHAR* inString, const WCHAR* inControl, WCHAR** ioContext ); + +extern size_t wcsnlen( const WCHAR* inString, size_t inMaxSize ); + +extern errno_t _itoa_s( int inValue, char* outBuffer, size_t inDestBufferSize, int inRadix ); +extern errno_t _itow_s( int inValue, WCHAR* outBuffer, size_t inDestBufferSize, int inRadix ); + +extern errno_t _ltoa_s( long inValue, char* outBuffer, size_t inDestBufferSize, int inRadix ); +extern errno_t _ltow_s( long inValue, WCHAR* outBuffer, size_t inDestBufferSize, int inRadix ); + +extern errno_t _ultoa_s( unsigned long inValue, char* outBuffer, size_t inDestBufferSize, int inRadix ); +extern errno_t _ultow_s( unsigned long inValue, WCHAR* outBuffer, size_t inDestBufferSize, int inRadix ); + +extern errno_t _i64toa_s( long long inValue, char* outBuffer, size_t inDestBufferSize, int inRadix ); +extern errno_t _i64tow_s( long long inValue, WCHAR* outBuffer, size_t inDestBufferSize, int inRadix ); + +extern errno_t _ui64toa_s( unsigned long long inValue, char* outBuffer, size_t inDestBufferSize, int inRadix ); +extern errno_t _ui64tow_s( unsigned long long inValue, WCHAR* outBuffer, size_t inDestBufferSize, int inRadix ); + +extern int sprintf_s( char *string, size_t sizeInBytes, const char *format, ... ); +extern int swprintf_s( WCHAR *string, size_t sizeInWords, const WCHAR *format, ... ); + +extern int _snprintf_s( char *string, size_t sizeInBytes, size_t count, const char *format, ... ); +extern int _snwprintf_s( WCHAR *string, size_t sizeInWords, size_t count, const WCHAR *format, ... ); + +extern int _vsprintf_s( char* string, size_t sizeInBytes, const char* format, va_list arglist ); +extern int _vsnprintf_s( char* string, size_t sizeInBytes, size_t count, const char* format, va_list arglist ); + +extern int _vswprintf_s( WCHAR* string, size_t sizeInWords, const WCHAR* format, va_list arglist ); +extern int _vsnwprintf_s( WCHAR* string, size_t sizeInWords, size_t count, const WCHAR* format, va_list arglist ); + +extern errno_t memcpy_s( void * dst, size_t sizeInBytes, const void * src, size_t count ); +extern errno_t memcpy_verw_s( void * dst, size_t sizeInBytes, const void * src, size_t count ); +extern errno_t memmove_s( void * dst, size_t sizeInBytes, const void * src, size_t count ); +extern errno_t memmove_verw_s( void * dst, size_t sizeInBytes, const void * src, size_t count ); + +#ifdef __cplusplus + } +#endif + +#endif /* MBUSAFECRT_H */ diff --git a/tee-worker/rust-sgx-sdk/common/inc/netdb.h b/tee-worker/rust-sgx-sdk/common/inc/netdb.h new file mode 100644 index 0000000000..264f90ff39 --- /dev/null +++ b/tee-worker/rust-sgx-sdk/common/inc/netdb.h @@ -0,0 +1,41 @@ +// +// Copyright © 2005-2020 Rich Felker, et al. +// Licensed under the MIT license.s +// + +/* Copyright © 2005-2020 Rich Felker, et al. + +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. */ + +#ifndef _NETDB_H +#define _NETDB_H + +struct addrinfo { + int ai_flags; + int ai_family; + int ai_socktype; + int ai_protocol; + socklen_t ai_addrlen; + struct sockaddr *ai_addr; + char *ai_canonname; + struct addrinfo *ai_next; +}; + +#endif diff --git a/tee-worker/rust-sgx-sdk/common/inc/poll.h b/tee-worker/rust-sgx-sdk/common/inc/poll.h new file mode 100644 index 0000000000..fc786fc279 --- /dev/null +++ b/tee-worker/rust-sgx-sdk/common/inc/poll.h @@ -0,0 +1,38 @@ +// +// Copyright © 2005-2020 Rich Felker, et al. +// Licensed under the MIT license. +// + +/* Copyright © 2005-2020 Rich Felker, et al. + +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. */ + +#ifndef _POLL_H_ +#define _POLL_H_ + +typedef unsigned long nfds_t; + +struct pollfd { + int fd; + short int events; + short int revents; +}; + +#endif diff --git a/tee-worker/rust-sgx-sdk/common/inc/pthread.h b/tee-worker/rust-sgx-sdk/common/inc/pthread.h new file mode 100644 index 0000000000..e79668ffd6 --- /dev/null +++ b/tee-worker/rust-sgx-sdk/common/inc/pthread.h @@ -0,0 +1,34 @@ +#ifndef _SYS_THREAD_H_ +#define _SYS_THREAD_H_ + +/* Thread identifiers. The structure of the attribute type is not + exposed on purpose. */ +typedef unsigned long int pthread_t; + +#if defined __x86_64__ && !defined __ILP32__ +# define __WORDSIZE 64 +#else +# define __WORDSIZE 32 +#define __WORDSIZE32_SIZE_ULONG 0 +#define __WORDSIZE32_PTRDIFF_LONG 0 +#endif + +#ifdef __x86_64__ +# if __WORDSIZE == 64 +# define __SIZEOF_PTHREAD_ATTR_T 56 +# else +# define __SIZEOF_PTHREAD_ATTR_T 32 +#endif + +union pthread_attr_t +{ + char __size[__SIZEOF_PTHREAD_ATTR_T]; + long int __align; +}; +#ifndef __have_pthread_attr_t +typedef union pthread_attr_t pthread_attr_t; +# define __have_pthread_attr_t 1 +#endif + +#endif +#endif diff --git a/tee-worker/rust-sgx-sdk/common/inc/pwd.h b/tee-worker/rust-sgx-sdk/common/inc/pwd.h new file mode 100644 index 0000000000..a45b145a94 --- /dev/null +++ b/tee-worker/rust-sgx-sdk/common/inc/pwd.h @@ -0,0 +1,40 @@ +// +// Copyright © 2005-2020 Rich Felker, et al. +// Licensed under the MIT license. +// + +/* Copyright © 2005-2020 Rich Felker, et al. + +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. */ + +#ifndef _PWD_H +#define _PWD_H + +struct passwd { + char *pw_name; + char *pw_passwd; + __uid_t pw_uid; + __gid_t pw_gid; + char *pw_gecos; + char *pw_dir; + char *pw_shell; +}; + +#endif diff --git a/tee-worker/rust-sgx-sdk/common/inc/sched.h b/tee-worker/rust-sgx-sdk/common/inc/sched.h new file mode 100644 index 0000000000..4d237c4044 --- /dev/null +++ b/tee-worker/rust-sgx-sdk/common/inc/sched.h @@ -0,0 +1,62 @@ +// +// Copyright © 2005-2020 Rich Felker, et al. +// Licensed under the MIT license.s +// + +/* Copyright © 2005-2020 Rich Felker, et al. + +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. */ + +#ifndef _SCHED_H +#define _SCHED_H +#include + +typedef struct { + unsigned long __bits[128/sizeof(long)]; +} cpu_set_t; + +#define __CPU_op_S(i, size, set, op) ( (i)/8U >= (size) ? 0 : \ + (((unsigned long *)(set))[(i)/8/sizeof(long)] op (1UL<<((i)%(8*sizeof(long))))) ) + +#define CPU_SET_S(i, size, set) __CPU_op_S(i, size, set, |=) +#define CPU_CLR_S(i, size, set) __CPU_op_S(i, size, set, &=~) +#define CPU_ISSET_S(i, size, set) __CPU_op_S(i, size, set, &) + +#define __CPU_op_func_S(func, op) \ +static __inline void __CPU_##func##_S(size_t __size, cpu_set_t *__dest, \ + const cpu_set_t *__src1, const cpu_set_t *__src2) \ +{ \ + size_t __i; \ + for (__i=0; __i<__size/sizeof(long); __i++) \ + ((unsigned long *)__dest)[__i] = ((unsigned long *)__src1)[__i] \ + op ((unsigned long *)__src2)[__i] ; \ +} + +__CPU_op_func_S(AND, &) +__CPU_op_func_S(OR, |) +__CPU_op_func_S(XOR, ^) + +#define CPU_AND_S(a,b,c,d) __CPU_AND_S(a,b,c,d) +#define CPU_OR_S(a,b,c,d) __CPU_OR_S(a,b,c,d) +#define CPU_XOR_S(a,b,c,d) __CPU_XOR_S(a,b,c,d) + +typedef __pid_t pid_t; + +#endif diff --git a/tee-worker/rust-sgx-sdk/common/inc/setjmp.h b/tee-worker/rust-sgx-sdk/common/inc/setjmp.h new file mode 100644 index 0000000000..752f0cf763 --- /dev/null +++ b/tee-worker/rust-sgx-sdk/common/inc/setjmp.h @@ -0,0 +1,65 @@ +/* $NetBSD: setjmp.h,v 1.26 2011/11/05 09:27:06 joerg Exp $ */ + +/*- + * Copyright (c) 1990, 1993 + * The Regents of the University of California. All rights reserved. + * (c) UNIX System Laboratories, Inc. + * All or some portions of this file are derived from material licensed + * to the University of California by American Telephone and Telegraph + * Co. or Unix System Laboratories, Inc. and are reproduced herein with + * the permission of UNIX System Laboratories, Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * @(#)setjmp.h 8.2 (Berkeley) 1/21/94 + */ + +#ifndef _SETJMP_H_ +#define _SETJMP_H_ + +#ifndef _JB_ATTRIBUTES +#define _JB_ATTRIBUTES /**/ +#else +#endif +#ifndef _BSD_JBSLOT_T_ +#define _BSD_JBSLOT_T_ long +#endif + +#define _JBLEN 8 + +typedef _BSD_JBSLOT_T_ jmp_buf[_JBLEN] _JB_ATTRIBUTES; + +#include +#define __returns_twice __attribute__((__returns_twice__)) +#define __dead + + +__BEGIN_DECLS +int setjmp(jmp_buf) __returns_twice; +void longjmp(jmp_buf, int) __dead; +__END_DECLS + +#endif /* !_SETJMP_H_ */ + diff --git a/tee-worker/rust-sgx-sdk/common/inc/signal.h b/tee-worker/rust-sgx-sdk/common/inc/signal.h new file mode 100644 index 0000000000..c0da74f456 --- /dev/null +++ b/tee-worker/rust-sgx-sdk/common/inc/signal.h @@ -0,0 +1,104 @@ +// +// Copyright © 2005-2020 Rich Felker, et al. +// Licensed under the MIT license.s +// + +/* Copyright © 2005-2020 Rich Felker, et al. + +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. */ + +#ifndef _SIGNAL_H +#define _SIGNAL_H + +#include + +typedef struct { + unsigned long _bits[128/sizeof(long)]; +} __sigset_t; + +typedef __sigset_t sigset_t; + +union sigval { + int sival_int; + void *sival_ptr; +}; + +typedef struct { + int si_signo; + int si_errno; + int si_code; + union { + char __pad[128 - 2*sizeof(int) - sizeof(long)]; + struct { + union { + struct { + __pid_t si_pid; + __uid_t si_uid; + } __piduid; + struct { + int si_timerid; + int si_overrun; + } __timer; + } __first; + union { + union sigval si_value; + struct { + int si_status; + __clock_t si_utime, si_stime; + } __sigchld; + } __second; + } __si_common; + struct { + void *si_addr; + short si_addr_lsb; + union { + struct { + void *si_lower; + void *si_upper; + } __addr_bnd; + unsigned si_pkey; + } __first; + } __sigfault; + struct { + long si_band; + int si_fd; + } __sigpoll; + struct { + void *si_call_addr; + int si_syscall; + unsigned si_arch; + } __sigsys; + } __si_fields; +} siginfo_t; + +struct sigaction { + union { + void (*sa_handler) (int); + void (*sa_sigaction) (int, siginfo_t *, void *); + } __sa_handler; + __sigset_t sa_mask; + int sa_flags; + void (*sa_restorer) (void); +}; + +#define sa_handler __sa_handler.sa_handler +#define sa_sigaction __sa_handler.sa_sigaction + +#endif diff --git a/tee-worker/rust-sgx-sdk/common/inc/stdarg.h b/tee-worker/rust-sgx-sdk/common/inc/stdarg.h new file mode 100644 index 0000000000..b2a5d36e82 --- /dev/null +++ b/tee-worker/rust-sgx-sdk/common/inc/stdarg.h @@ -0,0 +1,48 @@ +/* $OpenBSD: stdarg.h,v 1.14 2010/12/30 05:01:36 tedu Exp $ */ +/* $NetBSD: stdarg.h,v 1.12 1995/12/25 23:15:31 mycroft Exp $ */ + +/*- + * Copyright (c) 1991, 1993 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * @(#)stdarg.h 8.1 (Berkeley) 6/10/93 + */ + +#ifndef _STDARG_H_ +#define _STDARG_H_ + +#include +#include + +typedef __va_list va_list; + +#define va_start(ap, last) __builtin_va_start((ap), last) +#define va_end __builtin_va_end +#define va_arg __builtin_va_arg +#define va_copy(dst, src) __builtin_va_copy((dst),(src)) + +#endif /* !_STDARG_H_ */ diff --git a/tee-worker/rust-sgx-sdk/common/inc/stdbool.h b/tee-worker/rust-sgx-sdk/common/inc/stdbool.h new file mode 100644 index 0000000000..86b866d5d7 --- /dev/null +++ b/tee-worker/rust-sgx-sdk/common/inc/stdbool.h @@ -0,0 +1,44 @@ +/* $OpenBSD: stdbool.h,v 1.5 2010/07/24 22:17:03 guenther Exp $ */ + +/* + * Written by Marc Espie, September 25, 1999 + * Public domain. + */ + +#ifndef _STDBOOL_H_ +#define _STDBOOL_H_ + +#ifndef __cplusplus + +#ifndef __GNUC__ +/* Support for _C99: type _Bool is already built-in. */ +/* `_Bool' type must promote to `int' or `unsigned int'. */ +typedef enum { + false = 0, + true = 1 +} _Bool; + +/* And those constants must also be available as macros. */ +# define false false +# define true true +#else /* __GNUC__ */ +# define false 0 +# define true 1 +#endif + +/* User visible type `bool' is provided as a macro which may be redefined */ +#define bool _Bool + +#else /* __cplusplus */ + +# define _Bool bool +# define bool bool +# define false false +# define true true + +#endif + +/* Inform that everything is fine */ +#define __bool_true_false_are_defined 1 + +#endif /* _STDBOOL_H_ */ diff --git a/tee-worker/rust-sgx-sdk/common/inc/stddef.h b/tee-worker/rust-sgx-sdk/common/inc/stddef.h new file mode 100644 index 0000000000..c1328824c4 --- /dev/null +++ b/tee-worker/rust-sgx-sdk/common/inc/stddef.h @@ -0,0 +1,70 @@ +/* $OpenBSD: stddef.h,v 1.10 2009/09/22 21:40:02 jsg Exp $ */ +/* $NetBSD: stddef.h,v 1.4 1994/10/26 00:56:26 cgd Exp $ */ + +/*- + * Copyright (c) 1990 The Regents of the University of California. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * @(#)stddef.h 5.5 (Berkeley) 4/3/91 + */ + +#ifndef _STDDEF_H_ +#define _STDDEF_H_ + +#include +#include + +#ifndef _PTRDIFF_T_DEFINED_ +#define _PTRDIFF_T_DEFINED_ +typedef __ptrdiff_t ptrdiff_t; +#endif + +#ifndef _SIZE_T_DEFINED_ +#define _SIZE_T_DEFINED_ +typedef __size_t size_t; +#endif + +#if !defined(_WCHAR_T_DEFINED_) && !defined(__cplusplus) +#define _WCHAR_T_DEFINED_ +#ifndef __WCHAR_TYPE__ +#define __WCHAR_TYPE__ int +#endif +typedef __WCHAR_TYPE__ wchar_t; +#endif + +#ifndef NULL +#ifdef __cplusplus +#define NULL 0 +#else +#define NULL ((void *)0) +#endif +#endif + +#define offsetof(type, member) ((size_t)(&((type *)0)->member)) + +#endif /* _STDDEF_H_ */ + diff --git a/tee-worker/rust-sgx-sdk/common/inc/stdint.h b/tee-worker/rust-sgx-sdk/common/inc/stdint.h new file mode 100644 index 0000000000..e574484062 --- /dev/null +++ b/tee-worker/rust-sgx-sdk/common/inc/stdint.h @@ -0,0 +1,24 @@ +/* $OpenBSD: stdint.h,v 1.4 2006/12/10 22:17:55 deraadt Exp $ */ + +/* + * Copyright (c) 1997, 2005 Todd C. Miller + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef _STDINT_H_ +#define _STDINT_H_ + +#include + +#endif /* _STDINT_H_ */ diff --git a/tee-worker/rust-sgx-sdk/common/inc/stdio.h b/tee-worker/rust-sgx-sdk/common/inc/stdio.h new file mode 100644 index 0000000000..92d01a0d9e --- /dev/null +++ b/tee-worker/rust-sgx-sdk/common/inc/stdio.h @@ -0,0 +1,95 @@ +/* $OpenBSD: stdio.h,v 1.38 2009/11/09 00:18:27 kurt Exp $ */ +/* $NetBSD: stdio.h,v 1.18 1996/04/25 18:29:21 jtc Exp $ */ + +/*- + * Copyright (c) 1990 The Regents of the University of California. + * All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Chris Torek. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * @(#)stdio.h 5.17 (Berkeley) 6/3/91 + */ + +#ifndef _STDIO_H_ +#define _STDIO_H_ + +#include +#include + +#include + +#ifndef _SIZE_T_DEFINED_ +typedef __size_t size_t; +#define _SIZE_T_DEFINED_ +#endif + +#ifndef NULL +# ifdef __cplusplus +# define NULL 0 +# else +# define NULL ((void *)0) +# endif +#endif + +# define BUFSIZ 8192 + +#define EOF (-1) + +__BEGIN_DECLS + +int _TLIBC_CDECL_ snprintf(char *, size_t, const char *, ...) _GCC_PRINTF_FORMAT_(3, 4); +int _TLIBC_CDECL_ vsnprintf(char *, size_t, const char *, __va_list) _GCC_PRINTF_FORMAT_(3, 0); + +/* + * Deprecated definitions. + */ +#if 0 /* No FILE */ +_TLIBC_DEPRECATED_FUNCTION_(int _TLIBC_CDECL_, fprintf, FILE *, const char *, ...); +_TLIBC_DEPRECATED_FUNCTION_(int _TLIBC_CDECL_, putc, int, FILE *); +_TLIBC_DEPRECATED_FUNCTION_(int _TLIBC_CDECL_, fputc, int, FILE *); +_TLIBC_DEPRECATED_FUNCTION_(int _TLIBC_CDECL_, fputs, const char *, FILE *); +_TLIBC_DEPRECATED_FUNCTION_(int _TLIBC_CDECL_, fscanf, FILE *, const char *, ...); +_TLIBC_DEPRECATED_FUNCTION_(size_t _TLIBC_CDECL_, fwrite, const void *, size_t, size_t, FILE *); +_TLIBC_DEPRECATED_FUNCTION_(int _TLIBC_CDECL_, printf, const char *, ...); +_TLIBC_DEPRECATED_FUNCTION_(int _TLIBC_CDECL_, putchar, int); +_TLIBC_DEPRECATED_FUNCTION_(int _TLIBC_CDECL_, puts, const char *); +_TLIBC_DEPRECATED_FUNCTION_(int _TLIBC_CDECL_, scanf, const char *, ...); +_TLIBC_DEPRECATED_FUNCTION_(int _TLIBC_CDECL_, sprintf, char *, const char *, ...); +_TLIBC_DEPRECATED_FUNCTION_(int _TLIBC_CDECL_, sscanf, const char *, const char *, ...); +_TLIBC_DEPRECATED_FUNCTION_(int _TLIBC_CDECL_, vfprintf, FILE *, const char *, __va_list); +_TLIBC_DEPRECATED_FUNCTION_(int _TLIBC_CDECL_, vfscanf, FILE *, const char *, __va_list); +_TLIBC_DEPRECATED_FUNCTION_(int _TLIBC_CDECL_, vprintf, const char *, __va_list); +_TLIBC_DEPRECATED_FUNCTION_(int _TLIBC_CDECL_, vscanf, const char *, __va_list); +_TLIBC_DEPRECATED_FUNCTION_(int _TLIBC_CDECL_, vsprintf, char *, const char *, __va_list); +_TLIBC_DEPRECATED_FUNCTION_(int _TLIBC_CDECL_, vsscanf, const char *, const char *, __va_list); +#endif + +__END_DECLS + + +#endif /* !_STDIO_H_ */ diff --git a/tee-worker/rust-sgx-sdk/common/inc/stdlib.h b/tee-worker/rust-sgx-sdk/common/inc/stdlib.h new file mode 100644 index 0000000000..e5b6365341 --- /dev/null +++ b/tee-worker/rust-sgx-sdk/common/inc/stdlib.h @@ -0,0 +1,153 @@ +/* $OpenBSD: stdlib.h,v 1.47 2010/05/18 22:24:55 tedu Exp $ */ +/* $NetBSD: stdlib.h,v 1.25 1995/12/27 21:19:08 jtc Exp $ */ + +/*- +* Copyright (c) 1990 The Regents of the University of California. +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions +* are met: +* 1. Redistributions of source code must retain the above copyright +* notice, this list of conditions and the following disclaimer. +* 2. Redistributions in binary form must reproduce the above copyright +* notice, this list of conditions and the following disclaimer in the +* documentation and/or other materials provided with the distribution. +* 3. Neither the name of the University nor the names of its contributors +* may be used to endorse or promote products derived from this software +* without specific prior written permission. +* +* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND +* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +* ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE +* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +* SUCH DAMAGE. +* +* @(#)stdlib.h 5.13 (Berkeley) 6/4/91 +*/ + +#ifndef _STDLIB_H_ +#define _STDLIB_H_ + +#include +#include + +#ifndef _SIZE_T_DEFINED_ +#define _SIZE_T_DEFINED_ +typedef __size_t size_t; +#endif + +#if !defined(_WCHAR_T_DEFINED_) && !defined(__cplusplus) +#define _WCHAR_T_DEFINED_ +#ifndef __WCHAR_TYPE__ +#define __WCHAR_TYPE__ int +#endif +typedef __WCHAR_TYPE__ wchar_t; +#endif + +#ifndef _DIV_T_DEFINED +typedef struct { + int quot; /* quotient */ + int rem; /* remainder */ +} div_t; + +typedef struct { + long quot; /* quotient */ + long rem; /* remainder */ +} ldiv_t; + +typedef struct { + long long quot; /* quotient */ + long long rem; /* remainder */ +} lldiv_t; +#define _DIV_T_DEFINED +#endif + +#ifndef NULL +#ifdef __cplusplus +#define NULL 0 +#else +#define NULL ((void *)0) +#endif +#endif + +#define EXIT_FAILURE 1 +#define EXIT_SUCCESS 0 + +#define RAND_MAX 0x7fffffff +#define MB_CUR_MAX 1 + +__BEGIN_DECLS + +_TLIBC_NORETURN_ void _TLIBC_CDECL_ abort(void); +int _TLIBC_CDECL_ atexit(void (*)(void)); +int _TLIBC_CDECL_ abs(int); +double _TLIBC_CDECL_ atof(const char *); +int _TLIBC_CDECL_ atoi(const char *); +long _TLIBC_CDECL_ atol(const char *); +void * _TLIBC_CDECL_ bsearch(const void *, const void *, size_t, size_t, int (*)(const void *, const void *)); +void * _TLIBC_CDECL_ calloc(size_t, size_t); +div_t _TLIBC_CDECL_ div(int, int); +void _TLIBC_CDECL_ free(void *); +long _TLIBC_CDECL_ labs(long); +ldiv_t _TLIBC_CDECL_ ldiv(long, long); +void * _TLIBC_CDECL_ malloc(size_t); +void * _TLIBC_CDECL_ memalign(size_t, size_t); +void _TLIBC_CDECL_ qsort(void *, size_t, size_t, int (*)(const void *, const void *)); +void * _TLIBC_CDECL_ realloc(void *, size_t); +double _TLIBC_CDECL_ strtod(const char *, char **); +long _TLIBC_CDECL_ strtol(const char *, char **, int); +float _TLIBC_CDECL_ strtof(const char *, char **); + +long long + _TLIBC_CDECL_ atoll(const char *); +long long + _TLIBC_CDECL_ llabs(long long); +lldiv_t + _TLIBC_CDECL_ lldiv(long long, long long); +long long + _TLIBC_CDECL_ strtoll(const char *, char **, int); +unsigned long + _TLIBC_CDECL_ strtoul(const char *, char **, int); +long double + _TLIBC_CDECL_ strtold(const char *, char **); +unsigned long long + _TLIBC_CDECL_ strtoull(const char *, char **, int); + +int _TLIBC_CDECL_ mblen(const char *, size_t); +size_t _TLIBC_CDECL_ mbstowcs(wchar_t *, const char *, size_t); +int _TLIBC_CDECL_ wctomb(char *, wchar_t); +int _TLIBC_CDECL_ mbtowc(wchar_t *, const char *, size_t); +size_t _TLIBC_CDECL_ wcstombs(char *, const wchar_t *, size_t); + + +/* + * Deprecated C99. + */ +_TLIBC_DEPRECATED_FUNCTION_(int _TLIBC_CDECL_, atexit, void (_TLIBC_CDECL_ *)(void)); +_TLIBC_DEPRECATED_FUNCTION_(int _TLIBC_CDECL_, rand, void); +_TLIBC_DEPRECATED_FUNCTION_(void _TLIBC_CDECL_, srand, unsigned); +_TLIBC_DEPRECATED_FUNCTION_(void _TLIBC_CDECL_, exit, int); +_TLIBC_DEPRECATED_FUNCTION_(void _TLIBC_CDECL_, _Exit, int); +_TLIBC_DEPRECATED_FUNCTION_(char * _TLIBC_CDECL_, getenv, const char *); +_TLIBC_DEPRECATED_FUNCTION_(int _TLIBC_CDECL_, system, const char *); + +/* + * Non-C99 Functions. + */ +void * _TLIBC_CDECL_ alloca(size_t); + +/* + * Deprecated Non-C99. + */ +//_TLIBC_DEPRECATED_FUNCTION_(void _TLIBC_CDECL_, _exit, int); + +__END_DECLS + +#endif /* !_STDLIB_H_ */ diff --git a/tee-worker/rust-sgx-sdk/common/inc/string.h b/tee-worker/rust-sgx-sdk/common/inc/string.h new file mode 100644 index 0000000000..00a89fde77 --- /dev/null +++ b/tee-worker/rust-sgx-sdk/common/inc/string.h @@ -0,0 +1,130 @@ +/* $OpenBSD: string.h,v 1.20 2010/09/24 13:33:00 matthew Exp $ */ +/* $NetBSD: string.h,v 1.6 1994/10/26 00:56:30 cgd Exp $ */ + +/*- + * Copyright (c) 1990 The Regents of the University of California. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * @(#)string.h 5.10 (Berkeley) 3/9/91 + */ + +#ifndef _STRING_H_ +#define _STRING_H_ + +#include +#include + +#ifndef _SIZE_T_DEFINED_ +typedef __size_t size_t; +#define _SIZE_T_DEFINED_ +#endif + +#ifndef _ERRNO_T_DEFINED +#define _ERRNO_T_DEFINED +typedef int errno_t; +#endif + +#ifndef NULL +#ifdef __cplusplus +#define NULL 0 +#else +#define NULL ((void *)0) +#endif +#endif + +__BEGIN_DECLS + +void * _TLIBC_CDECL_ memchr(const void *, int, size_t); +int _TLIBC_CDECL_ memcmp(const void *, const void *, size_t); +void * _TLIBC_CDECL_ memcpy_nochecks(void *, const void *, size_t); +void * _TLIBC_CDECL_ memcpy(void *, const void *, size_t); +void * _TLIBC_CDECL_ memcpy_verw(void *, const void *, size_t); +void * _TLIBC_CDECL_ memmove(void *, const void *, size_t); +void * _TLIBC_CDECL_ memmove_verw(void *, const void *, size_t); +void * _TLIBC_CDECL_ memset(void *, int, size_t); +void * _TLIBC_CDECL_ memset_verw(void *, int, size_t); +char * _TLIBC_CDECL_ strchr(const char *, int); +int _TLIBC_CDECL_ strcmp(const char *, const char *); +int _TLIBC_CDECL_ strcoll(const char *, const char *); +size_t _TLIBC_CDECL_ strcspn(const char *, const char *); +char * _TLIBC_CDECL_ strerror(int); +size_t _TLIBC_CDECL_ strlen(const char *); +char * _TLIBC_CDECL_ strncat(char *, const char *, size_t); +int _TLIBC_CDECL_ strncmp(const char *, const char *, size_t); +char * _TLIBC_CDECL_ strncpy(char *, const char *, size_t); +char * _TLIBC_CDECL_ strpbrk(const char *, const char *); +char * _TLIBC_CDECL_ strrchr(const char *, int); +size_t _TLIBC_CDECL_ strspn(const char *, const char *); +char * _TLIBC_CDECL_ strstr(const char *, const char *); +char * _TLIBC_CDECL_ strtok(char *, const char *); +size_t _TLIBC_CDECL_ strxfrm(char *, const char *, size_t); +size_t _TLIBC_CDECL_ strlcpy(char *, const char *, size_t); +errno_t _TLIBC_CDECL_ memset_s(void *s, size_t smax, int c, size_t n); +errno_t _TLIBC_CDECL_ memset_verw_s(void *s, size_t smax, int c, size_t n); + +/* + * Deprecated C99. + */ +_TLIBC_DEPRECATED_FUNCTION_(char * _TLIBC_CDECL_, strcat, char *, const char *); +_TLIBC_DEPRECATED_FUNCTION_(char * _TLIBC_CDECL_, strcpy, char *, const char *); + +/* + * Common used non-C99 functions. + */ +char * _TLIBC_CDECL_ strndup(const char *, size_t); +size_t _TLIBC_CDECL_ strnlen(const char *, size_t); +int _TLIBC_CDECL_ consttime_memequal(const void *b1, const void *b2, size_t len); + +/* + * Non-C99 + */ +int _TLIBC_CDECL_ bcmp(const void *, const void *, size_t); +void _TLIBC_CDECL_ bcopy(const void *, void *, size_t); +void _TLIBC_CDECL_ bzero(void *, size_t); +char * _TLIBC_CDECL_ index(const char *, int); +void * _TLIBC_CDECL_ mempcpy(void *, const void *, size_t); +char * _TLIBC_CDECL_ rindex(const char *, int); +char * _TLIBC_CDECL_ stpncpy(char *dest, const char *src, size_t n); +int _TLIBC_CDECL_ strcasecmp(const char *, const char *); +int _TLIBC_CDECL_ strncasecmp(const char *, const char *, size_t); + +int _TLIBC_CDECL_ ffs(int); +int _TLIBC_CDECL_ ffsl(long int); +int _TLIBC_CDECL_ ffsll(long long int); + +char * _TLIBC_CDECL_ strtok_r(char *, const char *, char **); +int _TLIBC_CDECL_ strerror_r(int, char *, size_t); + +/* + * Deprecated Non-C99. + */ +_TLIBC_DEPRECATED_FUNCTION_(char * _TLIBC_CDECL_, strdup, const char *); +_TLIBC_DEPRECATED_FUNCTION_(char * _TLIBC_CDECL_, stpcpy, char *dest, const char *src); + +__END_DECLS + +#endif /* _STRING_H_ */ diff --git a/tee-worker/rust-sgx-sdk/common/inc/sys/_types.h b/tee-worker/rust-sgx-sdk/common/inc/sys/_types.h new file mode 100644 index 0000000000..5dc6d5bbfb --- /dev/null +++ b/tee-worker/rust-sgx-sdk/common/inc/sys/_types.h @@ -0,0 +1,168 @@ +/* $OpenBSD: _types.h,v 1.2 2008/03/16 19:42:57 otto Exp $ */ + +/*- + * Copyright (c) 1990, 1993 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * @(#)types.h 8.3 (Berkeley) 1/5/94 + */ + +#ifndef _SYS__TYPES_H_ +#define _SYS__TYPES_H_ + +#include +/* 7.18.1.1 Exact-width integer types */ +typedef signed char __int8_t; +typedef unsigned char __uint8_t; +typedef short __int16_t; +typedef unsigned short __uint16_t; +typedef int __int32_t; +typedef unsigned int __uint32_t; +#ifdef __x86_64__ +typedef long __int64_t; +typedef unsigned long __uint64_t; +#else +typedef long long __int64_t; +typedef unsigned long long __uint64_t; +#endif + +/* 7.18.1.2 Minimum-width integer types */ +typedef __int8_t __int_least8_t; +typedef __uint8_t __uint_least8_t; +typedef __int16_t __int_least16_t; +typedef __uint16_t __uint_least16_t; +typedef __int32_t __int_least32_t; +typedef __uint32_t __uint_least32_t; +typedef __int64_t __int_least64_t; +typedef __uint64_t __uint_least64_t; + +/* 7.18.1.3 Fastest minimum-width integer types */ +typedef __int8_t __int_fast8_t; +typedef __uint8_t __uint_fast8_t; +#ifdef __x86_64__ +/* Linux x86_64, from stdint.h */ +typedef long int __int_fast16_t; +typedef unsigned long int __uint_fast16_t; +typedef long int __int_fast32_t; +typedef unsigned long int __uint_fast32_t; +typedef long int __int_fast64_t; +typedef unsigned long int __uint_fast64_t; +#else +/* Android x86, and Linux x86 */ +typedef __int32_t __int_fast16_t; +typedef __uint32_t __uint_fast16_t; +typedef __int32_t __int_fast32_t; +typedef __uint32_t __uint_fast32_t; +typedef __int64_t __int_fast64_t; +typedef __uint64_t __uint_fast64_t; +#endif + +typedef long __off_t; +#ifdef __x86_64__ +typedef long int __off64_t; +#else +typedef long long int __off64_t; +#endif + +/* 7.18.1.4 Integer types capable of holding object pointers */ +#ifdef __i386__ +typedef __int32_t __intptr_t; +typedef __uint32_t __uintptr_t; +typedef __int32_t __ptrdiff_t; +/* Standard system types */ +typedef __uint32_t __size_t; +typedef __int32_t __ssize_t; +typedef long double __double_t; +typedef long double __float_t; +#else +typedef __int64_t __intptr_t; +typedef __uint64_t __uintptr_t; +typedef __int64_t __ptrdiff_t; + +/* Standard system types */ +typedef unsigned long __size_t; +typedef long __ssize_t; +typedef double __double_t; +typedef float __float_t; + +#endif /* !__i386__ */ + +typedef long __clock_t; + +typedef long __time_t; +typedef __builtin_va_list __va_list; +typedef unsigned int __wint_t; +/* wctype_t and wctrans_t are defined in wchar.h */ +typedef unsigned long int __wctype_t; +typedef int * __wctrans_t; + +/* + * mbstate_t is an opaque object to keep conversion state, during multibyte + * stream conversions. The content must not be referenced by user programs. + */ +/* For Linux, __mbstate_t is defined in wchar.h */ +typedef struct { + int __c; + union { + __wint_t __wc; + char __wcb[4]; + } __v; +} __mbstate_t; + +/* 7.18.1.5 Greatest-width integer types */ +typedef __int64_t __intmax_t; +typedef __uint64_t __uintmax_t; + + +typedef unsigned long int __ino_t; +typedef unsigned int __mode_t; +typedef unsigned int __uid_t; +typedef unsigned int __gid_t; +typedef long int __blksize_t; +typedef long int __blkcnt_t; + +#ifdef __x86_64__ +typedef unsigned long int __dev_t; +typedef long int __off64_t; +typedef unsigned long int __nlink_t; +typedef long int __blkcnt64_t; +typedef unsigned long int __ino64_t; +#else +typedef unsigned long long int __dev_t; +typedef long long int __off64_t; +typedef unsigned int __nlink_t; +typedef long long int __blkcnt64_t; +typedef unsigned long long int __ino64_t; +#endif + +typedef unsigned int __socklen_t; +typedef int __pid_t; +typedef long __cpu_mask; +#endif /* !_SYS__TYPES_H_ */ + + + diff --git a/tee-worker/rust-sgx-sdk/common/inc/sys/cdefs.h b/tee-worker/rust-sgx-sdk/common/inc/sys/cdefs.h new file mode 100644 index 0000000000..71c3c1ce22 --- /dev/null +++ b/tee-worker/rust-sgx-sdk/common/inc/sys/cdefs.h @@ -0,0 +1,132 @@ +/* $OpenBSD: cdefs.h,v 1.34 2012/08/14 20:11:37 matthew Exp $ */ +/* $NetBSD: cdefs.h,v 1.16 1996/04/03 20:46:39 christos Exp $ */ + +/* + * Copyright (c) 1991, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Berkeley Software Design, Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * @(#)cdefs.h 8.7 (Berkeley) 1/21/94 + */ + +#ifndef _SYS_CDEFS_H_ +#define _SYS_CDEFS_H_ + +/* Declaration field in C/C++ headers */ +#if defined(__cplusplus) +# define __BEGIN_DECLS extern "C" { +# define __END_DECLS } +#else +# define __BEGIN_DECLS +# define __END_DECLS +#endif + +#if defined(__STDC__) || defined(__cplusplus) +# define __CONCAT(x,y) x ## y +# define __STRING(x) #x +#else +# define __CONCAT(x,y) x/**/y +# define __STRING(x) "x" +#endif +/* + * Macro to test if we're using a specific version of gcc or later. + */ +#if defined __GNUC__ && defined __GNUC_MINOR_ +# define __GNUC_PREREQ__(ma, mi) \ + ((__GNUC__ > (ma)) || (__GNUC__ == (ma) && __GNUC_MINOR__ >= (mi))) +#else +# define __GNUC_PREREQ__(ma, mi) 0 +#endif + +/* Calling Convention: cdecl */ +#define _TLIBC_CDECL_ + +/* Thread Directive */ +#define _TLIBC_THREAD_ /* __thread */ + +/* Deprecated Warnings */ +#define _TLIBC_DEPRECATED_MSG(x) __STRING(x)" is deprecated in tlibc." +#define _TLIBC_DEPRECATED_(x) __attribute__((deprecated(_TLIBC_DEPRECATED_MSG(x)))) + +#ifndef _TLIBC_WARN_DEPRECATED_FUNCTIONS_ +# define _TLIBC_DEPRECATED_FUNCTION_(__ret, __func, ...) +#else +# define _TLIBC_DEPRECATED_FUNCTION_(__ret, __func, ...) \ + _TLIBC_DEPRECATED_(__func) \ + __ret __func(__VA_ARGS__) +#endif + +/* Static analysis for printf format strings. + * _MSC_PRINTF_FORMAT_: MSVC SAL annotation for specifying format strings. + * _GCC_PRINTF_FORMAT_(x, y): GCC declaring attribute for checking format strings. + * x - index of the format string. In C++ non-static method, index 1 is reseved for 'this'. + * y - index of first variadic agrument in '...'. + */ +#define _GCC_PRINTF_FORMAT_(x, y) __attribute__((__format__ (printf, x, y))) + +/* Attribute - noreturn */ +#define _TLIBC_NORETURN_ __attribute__ ((__noreturn__)) + +/* + * GNU C version 2.96 adds explicit branch prediction so that + * the CPU back-end can hint the processor and also so that + * code blocks can be reordered such that the predicted path + * sees a more linear flow, thus improving cache behavior, etc. + * + * The following two macros provide us with a way to utilize this + * compiler feature. Use __predict_true() if you expect the expression + * to evaluate to true, and __predict_false() if you expect the + * expression to evaluate to false. + * + * A few notes about usage: + * + * * Generally, __predict_false() error condition checks (unless + * you have some _strong_ reason to do otherwise, in which case + * document it), and/or __predict_true() `no-error' condition + * checks, assuming you want to optimize for the no-error case. + * + * * Other than that, if you don't know the likelihood of a test + * succeeding from empirical or other `hard' evidence, don't + * make predictions. + * + * * These are meant to be used in places that are run `a lot'. + * It is wasteful to make predictions in code that is run + * seldomly (e.g. at subsystem initialization time) as the + * basic block reordering that this affects can often generate + * larger code. + */ +#if defined(__GNUC__) && __GNUC_PREREQ__(2, 96) +#define __predict_true(exp) __builtin_expect(((exp) != 0), 1) +#define __predict_false(exp) __builtin_expect(((exp) != 0), 0) +#else +#define __predict_true(exp) ((exp) != 0) +#define __predict_false(exp) ((exp) != 0) +#endif + +#endif /* !_SYS_CDEFS_H_ */ diff --git a/tee-worker/rust-sgx-sdk/common/inc/sys/endian.h b/tee-worker/rust-sgx-sdk/common/inc/sys/endian.h new file mode 100644 index 0000000000..1cd7b810c3 --- /dev/null +++ b/tee-worker/rust-sgx-sdk/common/inc/sys/endian.h @@ -0,0 +1,54 @@ +/* $OpenBSD: endian.h,v 1.18 2006/03/27 07:09:24 otto Exp $ */ + +/*- + * Copyright (c) 1997 Niklas Hallqvist. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/* + * Generic definitions for little- and big-endian systems. Other endianesses + * has to be dealt with in the specific machine/endian.h file for that port. + * + * This file is meant to be included from a little- or big-endian port's + * machine/endian.h after setting _BYTE_ORDER to either 1234 for little endian + * or 4321 for big.. + */ + +#ifndef _SYS_ENDIAN_H_ +#define _SYS_ENDIAN_H_ + +#define _LITTLE_ENDIAN 1234 +#define _BIG_ENDIAN 4321 +#define _PDP_ENDIAN 3412 +#define _BYTE_ORDER _LITTLE_ENDIAN + +#define LITTLE_ENDIAN _LITTLE_ENDIAN +#define BIG_ENDIAN _BIG_ENDIAN +#define PDP_ENDIAN _PDP_ENDIAN +#define BYTE_ORDER _BYTE_ORDER + +#define __BYTE_ORDER _BYTE_ORDER +#define __BIG_ENDIAN _BIG_ENDIAN +#define __LITTLE_ENDIAN _LITTLE_ENDIAN + +#endif /* _SYS_ENDIAN_H_ */ + diff --git a/tee-worker/rust-sgx-sdk/common/inc/sys/epoll.h b/tee-worker/rust-sgx-sdk/common/inc/sys/epoll.h new file mode 100644 index 0000000000..958a4c4fb0 --- /dev/null +++ b/tee-worker/rust-sgx-sdk/common/inc/sys/epoll.h @@ -0,0 +1,42 @@ +// +// Copyright © 2005-2020 Rich Felker, et al. +// Licensed under the MIT license. +// + +/* Copyright © 2005-2020 Rich Felker, et al. + +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. */ + +#ifndef _SYS_EPOLL_H +#define _SYS_EPOLL_H + +typedef union epoll_data { + void *ptr; + int fd; + uint32_t u32; + uint64_t u64; +} epoll_data_t; + +struct epoll_event { + uint32_t events; + epoll_data_t data; +} __attribute__ ((__packed__)); + +#endif diff --git a/tee-worker/rust-sgx-sdk/common/inc/sys/ieee.h b/tee-worker/rust-sgx-sdk/common/inc/sys/ieee.h new file mode 100644 index 0000000000..eab3b9757d --- /dev/null +++ b/tee-worker/rust-sgx-sdk/common/inc/sys/ieee.h @@ -0,0 +1,145 @@ +/* $OpenBSD: ieee.h,v 1.2 2008/09/07 20:36:06 martynas Exp $ */ +/* $NetBSD: ieee.h,v 1.1 1996/09/30 16:34:25 ws Exp $ */ + +/* + * Copyright (c) 1992, 1993 + * The Regents of the University of California. All rights reserved. + * + * This software was developed by the Computer Systems Engineering group + * at Lawrence Berkeley Laboratory under DARPA contract BG 91-66 and + * contributed to Berkeley. + * + * All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the University of + * California, Lawrence Berkeley Laboratory. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * @(#)ieee.h 8.1 (Berkeley) 6/11/93 + */ + +/* + * ieee.h defines the machine-dependent layout of the machine's IEEE + * floating point. It does *not* define (yet?) any of the rounding + * mode bits, exceptions, and so forth. + */ + +/* + * Define the number of bits in each fraction and exponent. + * + * k k+1 + * Note that 1.0 x 2 == 0.1 x 2 and that denorms are represented + * + * (-exp_bias+1) + * as fractions that look like 0.fffff x 2 . This means that + * + * -126 + * the number 0.10000 x 2 , for instance, is the same as the normalized + * + * -127 -128 + * float 1.0 x 2 . Thus, to represent 2 , we need one leading zero + * + * -129 + * in the fraction; to represent 2 , we need two, and so on. This + * + * (-exp_bias-fracbits+1) + * implies that the smallest denormalized number is 2 + * + * for whichever format we are talking about: for single precision, for + * + * -126 -149 + * instance, we get .00000000000000000000001 x 2 , or 1.0 x 2 , and + * + * -149 == -127 - 23 + 1. + */ + +#include + +#define SNG_EXPBITS 8 +#define SNG_FRACBITS 23 + +#define DBL_EXPBITS 11 +#define DBL_FRACHBITS 20 +#define DBL_FRACLBITS 32 +#define DBL_FRACBITS 52 + +#define EXT_EXPBITS 15 +#define EXT_FRACHBITS 32 +#define EXT_FRACLBITS 32 +#define EXT_FRACBITS 64 + +#define EXT_TO_ARRAY32(p, a) do { \ + (a)[0] = (uint32_t)(p)->ext_fracl; \ + (a)[1] = (uint32_t)(p)->ext_frach; \ +} while(0) + +struct ieee_single { + u_int sng_frac:23; + u_int sng_exp:8; + u_int sng_sign:1; +}; + +struct ieee_double { + u_int dbl_fracl; + u_int dbl_frach:20; + u_int dbl_exp:11; + u_int dbl_sign:1; +}; + +struct ieee_ext { + u_int ext_fracl; + u_int ext_frach; + u_int ext_exp:15; + u_int ext_sign:1; + u_int ext_padl:16; + u_int ext_padh; +}; + +/* + * Floats whose exponent is in [1..INFNAN) (of whatever type) are + * `normal'. Floats whose exponent is INFNAN are either Inf or NaN. + * Floats whose exponent is zero are either zero (iff all fraction + * bits are zero) or subnormal values. + * + * A NaN is a `signalling NaN' if its QUIETNAN bit is clear in its + * high fraction; if the bit is set, it is a `quiet NaN'. + */ +#define SNG_EXP_INFNAN 255 +#define DBL_EXP_INFNAN 2047 +#define EXT_EXP_INFNAN 32767 + +#if 0 +#define SNG_QUIETNAN (1 << 22) +#define DBL_QUIETNAN (1 << 19) +#define EXT_QUIETNAN (1 << 15) +#endif + +/* + * Exponent biases. + */ +#define SNG_EXP_BIAS 127 +#define DBL_EXP_BIAS 1023 +#define EXT_EXP_BIAS 16383 diff --git a/tee-worker/rust-sgx-sdk/common/inc/sys/limits.h b/tee-worker/rust-sgx-sdk/common/inc/sys/limits.h new file mode 100644 index 0000000000..3d1f9673ad --- /dev/null +++ b/tee-worker/rust-sgx-sdk/common/inc/sys/limits.h @@ -0,0 +1,77 @@ +/* $OpenBSD: limits.h,v 1.8 2009/11/27 19:54:35 guenther Exp $ */ +/* + * Copyright (c) 2002 Marc Espie. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE OPENBSD PROJECT AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE OPENBSD + * PROJECT OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#ifndef _SYS_LIMITS_H_ +#define _SYS_LIMITS_H_ + +#include + +/* Common definitions for limits.h. */ + +#define CHAR_BIT 8 /* number of bits in a char */ + +#define SCHAR_MAX 0x7f /* max value for a signed char */ +#define SCHAR_MIN (-0x7f - 1) /* min value for a signed char */ + +#define UCHAR_MAX 0xff /* max value for an unsigned char */ +#ifdef __CHAR_UNSIGNED__ +# define CHAR_MIN 0 /* min value for a char */ +# define CHAR_MAX 0xff /* max value for a char */ +#else +# define CHAR_MAX 0x7f +# define CHAR_MIN (-0x7f-1) +#endif + +#define MB_LEN_MAX 1 /* Allow UTF-8 (RFC 3629) */ + +#define USHRT_MAX 0xffff /* max value for an unsigned short */ +#define SHRT_MAX 0x7fff /* max value for a short */ +#define SHRT_MIN (-0x7fff-1) /* min value for a short */ + +#define UINT_MAX 0xffffffffU /* max value for an unsigned int */ +#define INT_MAX 0x7fffffff /* max value for an int */ +#define INT_MIN (-0x7fffffff-1) /* min value for an int */ + +#ifdef __x86_64__ +# define ULONG_MAX 0xffffffffffffffffUL /* max value for unsigned long */ +# define LONG_MAX 0x7fffffffffffffffL /* max value for a signed long */ +# define LONG_MIN (-0x7fffffffffffffffL-1) /* min value for a signed long */ +#else +# define ULONG_MAX 0xffffffffUL /* max value for an unsigned long */ +# define LONG_MAX 0x7fffffffL /* max value for a long */ +# define LONG_MIN (-0x7fffffffL-1) /* min value for a long */ +#endif + +#define ULLONG_MAX 0xffffffffffffffffULL /* max value for unsigned long long */ +#define LLONG_MAX 0x7fffffffffffffffLL /* max value for a signed long long */ +#define LLONG_MIN (-0x7fffffffffffffffLL-1) /* min value for a signed long long */ + +#ifdef __x86_64__ +# define LONG_BIT 64 +#else +# define LONG_BIT 32 +#endif + +#endif /* !_SYS_LIMITS_H_ */ diff --git a/tee-worker/rust-sgx-sdk/common/inc/sys/sockaddr.h b/tee-worker/rust-sgx-sdk/common/inc/sys/sockaddr.h new file mode 100644 index 0000000000..ba6811cbf7 --- /dev/null +++ b/tee-worker/rust-sgx-sdk/common/inc/sys/sockaddr.h @@ -0,0 +1,32 @@ +// +// Copyright © 2005-2020 Rich Felker, et al. +// Licensed under the MIT license. +// + +/* Copyright © 2005-2020 Rich Felker, et al. + +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. */ + +#ifndef _SYS_SOCKADDR_H_ +#define _SYS_SOCKADDR_H_ + +typedef unsigned short int sa_family_t; + +#endif diff --git a/tee-worker/rust-sgx-sdk/common/inc/sys/socket.h b/tee-worker/rust-sgx-sdk/common/inc/sys/socket.h new file mode 100644 index 0000000000..0b16699cc6 --- /dev/null +++ b/tee-worker/rust-sgx-sdk/common/inc/sys/socket.h @@ -0,0 +1,54 @@ +// +// Copyright © 2005-2020 Rich Felker, et al. +// Licensed under the MIT license. +// + +/* Copyright © 2005-2020 Rich Felker, et al. + +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. */ + +#ifndef _SYS_SOCKET_H_ +#define _SYS_SOCKET_H_ + +#include +#include +#include + +typedef __socklen_t socklen_t; + +struct sockaddr { + sa_family_t sa_family; + char sa_data[14]; +}; + +struct msghdr { + void *msg_name; + socklen_t msg_namelen; + + struct iovec *msg_iov; + size_t msg_iovlen; + + void *msg_control; + size_t msg_controllen; + + int msg_flags; +}; + +#endif diff --git a/tee-worker/rust-sgx-sdk/common/inc/sys/stat.h b/tee-worker/rust-sgx-sdk/common/inc/sys/stat.h new file mode 100644 index 0000000000..1cf090a7a1 --- /dev/null +++ b/tee-worker/rust-sgx-sdk/common/inc/sys/stat.h @@ -0,0 +1,127 @@ +// +// Copyright © 2005-2020 Rich Felker, et al. +// Licensed under the MIT license. +// + +/* Copyright © 2005-2020 Rich Felker, et al. + +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. */ + + +#ifndef _SYS_STAT_H_ +#define _SYS_STAT_H_ + +#include +#include +#include + +typedef __dev_t dev_t; +typedef __ino_t ino_t; +typedef __ino64_t ino64_t; +typedef __mode_t mode_t; +typedef __nlink_t nlink_t; +typedef __uid_t uid_t; +typedef __gid_t gid_t; +typedef __blksize_t blksize_t; +typedef __blkcnt_t blkcnt_t; +typedef __blkcnt64_t blkcnt64_t; + +struct stat { + dev_t st_dev; + ino_t st_ino; + nlink_t st_nlink; + + mode_t st_mode; + uid_t st_uid; + gid_t st_gid; + unsigned int __pad0; + dev_t st_rdev; + off_t st_size; + blksize_t st_blksize; + blkcnt_t st_blocks; + + struct timespec st_atim; + struct timespec st_mtim; + struct timespec st_ctim; + long __unused[3]; +}; + +struct stat64 { + dev_t st_dev; + ino64_t st_ino; + nlink_t st_nlink; + + mode_t st_mode; + uid_t st_uid; + gid_t st_gid; + unsigned int __pad0; + dev_t st_rdev; + off_t st_size; + blksize_t st_blksize; + blkcnt64_t st_blocks; + + struct timespec st_atim; + struct timespec st_mtim; + struct timespec st_ctim; + long __unused[3]; +}; + +#define S_IFMT 0170000 + +#define S_IFDIR 0040000 +#define S_IFCHR 0020000 +#define S_IFBLK 0060000 +#define S_IFREG 0100000 +#define S_IFIFO 0010000 +#define S_IFLNK 0120000 +#define S_IFSOCK 0140000 + +#define S_TYPEISMQ(buf) 0 +#define S_TYPEISSEM(buf) 0 +#define S_TYPEISSHM(buf) 0 +#define S_TYPEISTMO(buf) 0 + +#define S_ISDIR(mode) (((mode) & S_IFMT) == S_IFDIR) +#define S_ISCHR(mode) (((mode) & S_IFMT) == S_IFCHR) +#define S_ISBLK(mode) (((mode) & S_IFMT) == S_IFBLK) +#define S_ISREG(mode) (((mode) & S_IFMT) == S_IFREG) +#define S_ISFIFO(mode) (((mode) & S_IFMT) == S_IFIFO) +#define S_ISLNK(mode) (((mode) & S_IFMT) == S_IFLNK) +#define S_ISSOCK(mode) (((mode) & S_IFMT) == S_IFSOCK) + +#ifndef S_IRUSR +#define S_ISUID 04000 +#define S_ISGID 02000 +#define S_ISVTX 01000 +#define S_IRUSR 0400 +#define S_IWUSR 0200 +#define S_IXUSR 0100 +#define S_IRWXU 0700 +#define S_IRGRP 0040 +#define S_IWGRP 0020 +#define S_IXGRP 0010 +#define S_IRWXG 0070 +#define S_IROTH 0004 +#define S_IWOTH 0002 +#define S_IXOTH 0001 +#define S_IRWXO 0007 +#endif + +#endif /* _SYS_STAT_H_ */ diff --git a/tee-worker/rust-sgx-sdk/common/inc/sys/stdint.h b/tee-worker/rust-sgx-sdk/common/inc/sys/stdint.h new file mode 100644 index 0000000000..51599456d5 --- /dev/null +++ b/tee-worker/rust-sgx-sdk/common/inc/sys/stdint.h @@ -0,0 +1,260 @@ +/* $OpenBSD: stdint.h,v 1.4 2006/12/10 22:17:55 deraadt Exp $ */ + +/* + * Copyright (c) 1997, 2005 Todd C. Miller + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef _SYS_STDINT_H_ +#define _SYS_STDINT_H_ + +#include +#include + +/* 7.18.1.1 Exact-width integer types (also in sys/types.h) */ +#ifndef _INT8_T_DEFINED_ +#define _INT8_T_DEFINED_ +typedef __int8_t int8_t; +#endif + +#ifndef _UINT8_T_DEFINED_ +#define _UINT8_T_DEFINED_ +typedef __uint8_t uint8_t; +#endif + +#ifndef _INT16_T_DEFINED_ +#define _INT16_T_DEFINED_ +typedef __int16_t int16_t; +#endif + +#ifndef _UINT16_T_DEFINED_ +#define _UINT16_T_DEFINED_ +typedef __uint16_t uint16_t; +#endif + +#ifndef _INT32_T_DEFINED_ +#define _INT32_T_DEFINED_ +typedef __int32_t int32_t; +#endif + +#ifndef _UINT32_T_DEFINED_ +#define _UINT32_T_DEFINED_ +typedef __uint32_t uint32_t; +#endif + +#ifndef _INT64_T_DEFINED_ +#define _INT64_T_DEFINED_ +typedef __int64_t int64_t; +#endif + +#ifndef _UINT64_T_DEFINED_ +#define _UINT64_T_DEFINED_ +typedef __uint64_t uint64_t; +#endif + +/* 7.18.1.2 Minimum-width integer types */ +typedef __int_least8_t int_least8_t; +typedef __uint_least8_t uint_least8_t; +typedef __int_least16_t int_least16_t; +typedef __uint_least16_t uint_least16_t; +typedef __int_least32_t int_least32_t; +typedef __uint_least32_t uint_least32_t; +typedef __int_least64_t int_least64_t; +typedef __uint_least64_t uint_least64_t; + +/* 7.18.1.3 Fastest minimum-width integer types */ +typedef __int_fast8_t int_fast8_t; +typedef __uint_fast8_t uint_fast8_t; +typedef __int_fast16_t int_fast16_t; +typedef __uint_fast16_t uint_fast16_t; +typedef __int_fast32_t int_fast32_t; +typedef __uint_fast32_t uint_fast32_t; +typedef __int_fast64_t int_fast64_t; +typedef __uint_fast64_t uint_fast64_t; + +/* 7.18.1.4 Integer types capable of holding object pointers */ +#ifndef _INTPTR_T_DEFINED_ +#define _INTPTR_T_DEFINED_ +typedef __intptr_t intptr_t; +#endif + +#ifndef _UINTPTR_T_DEFINED_ +#define _UINTPTR_T_DEFINED_ +typedef __uintptr_t uintptr_t; +#endif + +/* 7.18.1.5 Greatest-width integer types */ +typedef __intmax_t intmax_t; +typedef __uintmax_t uintmax_t; + +//#if !defined(__cplusplus) || defined(__STDC_LIMIT_MACROS) +/* + * 7.18.2 Limits of specified-width integer types. + * + * The following object-like macros specify the minimum and maximum limits + * of integer types corresponding to the typedef names defined above. + */ + +/* 7.18.2.1 Limits of exact-width integer types */ +#define INT8_MIN (-0x7f - 1) +#define INT16_MIN (-0x7fff - 1) +#define INT32_MIN (-0x7fffffff - 1) +#ifdef __x86_64__ +#define INT64_MIN (-0x7fffffffffffffffL - 1) +#else +#define INT64_MIN (-0x7fffffffffffffffLL - 1) +#endif + +#define INT8_MAX 0x7f +#define INT16_MAX 0x7fff +#define INT32_MAX 0x7fffffff +#ifdef __x86_64__ +#define INT64_MAX 0x7fffffffffffffffL +#else +#define INT64_MAX 0x7fffffffffffffffLL +#endif + +#define UINT8_MAX 0xff +#define UINT16_MAX 0xffff +#define UINT32_MAX 0xffffffffU +#ifdef __x86_64__ +#define UINT64_MAX 0xffffffffffffffffUL +#else +#define UINT64_MAX 0xffffffffffffffffULL +#endif + +/* 7.18.2.2 Limits of minimum-width integer types */ +#define INT_LEAST8_MIN INT8_MIN +#define INT_LEAST16_MIN INT16_MIN +#define INT_LEAST32_MIN INT32_MIN +#define INT_LEAST64_MIN INT64_MIN + +#define INT_LEAST8_MAX INT8_MAX +#define INT_LEAST16_MAX INT16_MAX +#define INT_LEAST32_MAX INT32_MAX +#define INT_LEAST64_MAX INT64_MAX + +#define UINT_LEAST8_MAX UINT8_MAX +#define UINT_LEAST16_MAX UINT16_MAX +#define UINT_LEAST32_MAX UINT32_MAX +#define UINT_LEAST64_MAX UINT64_MAX + +/* 7.18.2.3 Limits of fastest minimum-width integer types */ +#define INT_FAST8_MIN INT8_MIN +#define INT_FAST16_MIN INT16_MIN +#define INT_FAST32_MIN INT32_MIN +#define INT_FAST64_MIN INT64_MIN + +#define INT_FAST8_MAX INT8_MAX +#ifdef __x86_64__ +#define INT_FAST16_MAX INT64_MAX +#define INT_FAST32_MAX INT64_MAX +#else +#define INT_FAST16_MAX INT32_MAX +#define INT_FAST32_MAX INT32_MAX +#endif +#define INT_FAST64_MAX INT64_MAX + +#define UINT_FAST8_MAX UINT8_MAX +#ifdef __x86_64__ +#define UINT_FAST16_MAX UINT64_MAX +#define UINT_FAST32_MAX UINT64_MAX +#else +#define UINT_FAST16_MAX UINT32_MAX +#define UINT_FAST32_MAX UINT32_MAX +#endif +#define UINT_FAST64_MAX UINT64_MAX + +/* 7.18.2.4 Limits of integer types capable of holding object pointers */ +#ifdef __x86_64__ +#define INTPTR_MIN INT64_MIN +#define INTPTR_MAX INT64_MAX +#define UINTPTR_MAX UINT64_MAX +#else +#define INTPTR_MIN INT32_MIN +#define INTPTR_MAX INT32_MAX +#define UINTPTR_MAX UINT32_MAX +#endif + +/* 7.18.2.5 Limits of greatest-width integer types */ +#define INTMAX_MIN INT64_MIN +#define INTMAX_MAX INT64_MAX +#define UINTMAX_MAX UINT64_MAX + +/* + * 7.18.3 Limits of other integer types. + * + * The following object-like macros specify the minimum and maximum limits + * of integer types corresponding to types specified in other standard + * header files. + */ + +/* Limits of ptrdiff_t */ +#define PTRDIFF_MIN INTPTR_MIN +#define PTRDIFF_MAX INTPTR_MAX + +/* Limits of size_t (also in limits.h) */ +#ifndef SIZE_MAX +#define SIZE_MAX UINTPTR_MAX +#endif + +/* Limits of wchar_t */ +# ifdef __WCHAR_MAX__ +# define WCHAR_MAX __WCHAR_MAX__ +# else +# define WCHAR_MAX (2147483647) +# endif +# ifdef __WCHAR_MIN__ +# define WCHAR_MIN __WCHAR_MIN__ +# elif L'\0' - 1 > 0 +# define WCHAR_MIN L'\0' +# else +# define WCHAR_MIN (-WCHAR_MAX - 1) +# endif + +/* Limits of wint_t */ +# define WINT_MIN (0u) +# define WINT_MAX (4294967295u) + +//#endif /* __cplusplus || __STDC_LIMIT_MACROS */ + +//#if !defined(__cplusplus) || defined(__STDC_CONSTANT_MACROS) +/* + * 7.18.4 Macros for integer constants. + * + * The following function-like macros expand to integer constants + * suitable for initializing objects that have integer types corresponding + * to types defined in . The argument in any instance of + * these macros shall be a decimal, octal, or hexadecimal constant with + * a value that does not exceed the limits for the corresponding type. + */ + +/* 7.18.4.1 Macros for minimum-width integer constants. */ +#define INT8_C(_c) (_c) +#define INT16_C(_c) (_c) +#define INT32_C(_c) (_c) +#define INT64_C(_c) __CONCAT(_c, LL) + +#define UINT8_C(_c) (_c) +#define UINT16_C(_c) (_c) +#define UINT32_C(_c) __CONCAT(_c, U) +#define UINT64_C(_c) __CONCAT(_c, ULL) + +/* 7.18.4.2 Macros for greatest-width integer constants. */ +#define INTMAX_C(_c) __CONCAT(_c, LL) +#define UINTMAX_C(_c) __CONCAT(_c, ULL) + +//#endif /* __cplusplus || __STDC_CONSTANT_MACROS */ + +#endif /* _SYS_STDINT_H_ */ diff --git a/tee-worker/rust-sgx-sdk/common/inc/sys/struct_timespec.h b/tee-worker/rust-sgx-sdk/common/inc/sys/struct_timespec.h new file mode 100644 index 0000000000..bca02c8809 --- /dev/null +++ b/tee-worker/rust-sgx-sdk/common/inc/sys/struct_timespec.h @@ -0,0 +1,37 @@ +// +// Copyright © 2005-2020 Rich Felker, et al. +// Licensed under the MIT license. +// + +/* Copyright © 2005-2020 Rich Felker, et al. + +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. */ + +#ifndef _SYS_TIMESPEC_H_ +#define _SYS_TIMESPEC_H_ + +#include + +struct timespec { + __time_t tv_sec; + long tv_nsec; +}; + +#endif diff --git a/tee-worker/rust-sgx-sdk/common/inc/sys/types.h b/tee-worker/rust-sgx-sdk/common/inc/sys/types.h new file mode 100644 index 0000000000..b64f89df04 --- /dev/null +++ b/tee-worker/rust-sgx-sdk/common/inc/sys/types.h @@ -0,0 +1,129 @@ +/* $OpenBSD: types.h,v 1.31 2008/03/16 19:42:57 otto Exp $ */ +/* $NetBSD: types.h,v 1.29 1996/11/15 22:48:25 jtc Exp $ */ + +/*- + * Copyright (c) 1982, 1986, 1991, 1993 + * The Regents of the University of California. All rights reserved. + * (c) UNIX System Laboratories, Inc. + * All or some portions of this file are derived from material licensed + * to the University of California by American Telephone and Telegraph + * Co. or Unix System Laboratories, Inc. and are reproduced herein with + * the permission of UNIX System Laboratories, Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * @(#)types.h 8.4 (Berkeley) 1/21/94 + */ + +#ifndef _SYS_TYPES_H_ +#define _SYS_TYPES_H_ + +#include +#include + +typedef unsigned char u_char; +typedef unsigned short u_short; +typedef unsigned int u_int; +typedef unsigned long u_long; + +typedef unsigned char unchar; /* Sys V compatibility */ +typedef unsigned short ushort; /* Sys V compatibility */ +typedef unsigned int uint; /* Sys V compatibility */ +typedef unsigned long ulong; /* Sys V compatibility */ + +#ifndef _INT8_T_DEFINED_ +#define _INT8_T_DEFINED_ +typedef __int8_t int8_t; +#endif + +#ifndef _UINT8_T_DEFINED_ +#define _UINT8_T_DEFINED_ +typedef __uint8_t uint8_t; +#endif + +#ifndef _INT16_T_DEFINED_ +#define _INT16_T_DEFINED_ +typedef __int16_t int16_t; +#endif + +#ifndef _UINT16_T_DEFINED_ +#define _UINT16_T_DEFINED_ +typedef __uint16_t uint16_t; +#endif + +#ifndef _INT32_T_DEFINED_ +#define _INT32_T_DEFINED_ +typedef __int32_t int32_t; +#endif + +#ifndef _UINT32_T_DEFINED_ +#define _UINT32_T_DEFINED_ +typedef __uint32_t uint32_t; +#endif + +#ifndef _INT64_T_DEFINED_ +#define _INT64_T_DEFINED_ +typedef __int64_t int64_t; +#endif + +#ifndef _UINT64_T_DEFINED_ +#define _UINT64_T_DEFINED_ +typedef __uint64_t uint64_t; +#endif + +#ifndef _INTPTR_T_DEFINED_ +#define _INTPTR_T_DEFINED_ +typedef __intptr_t intptr_t; +#endif + +#ifndef _UINTPTR_T_DEFINED_ +#define _UINTPTR_T_DEFINED_ +typedef __uintptr_t uintptr_t; +#endif + +/* BSD-style unsigned bits types */ +typedef __uint8_t u_int8_t; +typedef __uint16_t u_int16_t; +typedef __uint32_t u_int32_t; +typedef __uint64_t u_int64_t; + + +#ifndef _SIZE_T_DEFINED_ +#define _SIZE_T_DEFINED_ +typedef __size_t size_t; +#endif + +#ifndef _SSIZE_T_DEFINED_ +#define _SSIZE_T_DEFINED_ +typedef __ssize_t ssize_t; +#endif + +#ifndef _OFF_T_DEFINED_ +#define _OFF_T_DEFINED_ +typedef __off_t off_t; +typedef __off64_t off64_t; +#endif + +#endif /* !_SYS_TYPES_H_ */ diff --git a/tee-worker/rust-sgx-sdk/common/inc/sys/uio.h b/tee-worker/rust-sgx-sdk/common/inc/sys/uio.h new file mode 100644 index 0000000000..2544f06a7d --- /dev/null +++ b/tee-worker/rust-sgx-sdk/common/inc/sys/uio.h @@ -0,0 +1,35 @@ +// +// Copyright © 2005-2020 Rich Felker, et al. +// Licensed under the MIT license. +// + +/* Copyright © 2005-2020 Rich Felker, et al. + +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. */ + +#ifndef _SYS_UIO_H_ +#define _SYS_UIO_H_ + +struct iovec { + void *iov_base; + size_t iov_len; +}; + +#endif diff --git a/tee-worker/rust-sgx-sdk/common/inc/time.h b/tee-worker/rust-sgx-sdk/common/inc/time.h new file mode 100644 index 0000000000..01cfd6e4e9 --- /dev/null +++ b/tee-worker/rust-sgx-sdk/common/inc/time.h @@ -0,0 +1,105 @@ +/* $OpenBSD: time.h,v 1.18 2006/01/06 18:53:04 millert Exp $ */ +/* $NetBSD: time.h,v 1.9 1994/10/26 00:56:35 cgd Exp $ */ + +/* + * Copyright (c) 1989 The Regents of the University of California. + * All rights reserved. + * + * (c) UNIX System Laboratories, Inc. + * All or some portions of this file are derived from material licensed + * to the University of California by American Telephone and Telegraph + * Co. or Unix System Laboratories, Inc. and are reproduced herein with + * the permission of UNIX System Laboratories, Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * @(#)time.h 5.12 (Berkeley) 3/9/91 + */ + +#ifndef _TIME_H_ +#define _TIME_H_ + +#include +#include +#include + +#ifndef NULL +#ifdef __cplusplus +#define NULL 0 +#else +#define NULL ((void *)0) +#endif +#endif + +#if !defined (_CLOCK_T_DEFINED_) && !defined (_CLOCK_T_DEFINED) +#define _CLOCK_T_DEFINED_ +#define _CLOCK_T_DEFINED +typedef __clock_t clock_t; +#endif + +#if !defined (_TIME_T_DEFINED_) && !defined (_TIME_T_DEFINED) +#define _TIME_T_DEFINED_ +#define _TIME_T_DEFINED +typedef __time_t time_t; +#endif + +#if !defined (_SIZE_T_DEFINED_) && !defined (_SIZE_T_DEFINED) +#define _SIZE_T_DEFINED_ +#define _SIZE_T_DEFINED +typedef __size_t size_t; +#endif + +#if !defined (_TM_DEFINED) +#define _TM_DEFINED +struct tm { + int tm_sec; /* seconds after the minute [0-60] */ + int tm_min; /* minutes after the hour [0-59] */ + int tm_hour; /* hours since midnight [0-23] */ + int tm_mday; /* day of the month [1-31] */ + int tm_mon; /* months since January [0-11] */ + int tm_year; /* years since 1900 */ + int tm_wday; /* days since Sunday [0-6] */ + int tm_yday; /* days since January 1 [0-365] */ + int tm_isdst; /* Daylight Saving Time flag */ + /* FIXME: naming issue exists on Fedora/Ubuntu */ + long tm_gmtoff; /* offset from UTC in seconds */ + char *tm_zone; /* timezone abbreviation */ +}; +#endif + +__BEGIN_DECLS + +double _TLIBC_CDECL_ difftime(time_t, time_t); +char * _TLIBC_CDECL_ asctime(const struct tm *); +size_t _TLIBC_CDECL_ strftime(char *, size_t, const char *, const struct tm *); + +/* + * Non-C99 + */ +char * _TLIBC_CDECL_ asctime_r(const struct tm *, char *); + +__END_DECLS + +#endif /* !_TIME_H_ */ diff --git a/tee-worker/rust-sgx-sdk/common/inc/unistd.h b/tee-worker/rust-sgx-sdk/common/inc/unistd.h new file mode 100644 index 0000000000..2ab3a9a042 --- /dev/null +++ b/tee-worker/rust-sgx-sdk/common/inc/unistd.h @@ -0,0 +1,59 @@ +/* $OpenBSD: unistd.h,v 1.62 2008/06/25 14:58:54 millert Exp $ */ +/* $NetBSD: unistd.h,v 1.26.4.1 1996/05/28 02:31:51 mrg Exp $ */ + +/*- + * Copyright (c) 1991 The Regents of the University of California. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * @(#)unistd.h 5.13 (Berkeley) 6/17/91 + */ + +#ifndef _UNISTD_H_ +#define _UNISTD_H_ + +#include +#include + +__BEGIN_DECLS + +void * _TLIBC_CDECL_ sbrk(intptr_t); + +/* + * Deprecated Non-C99. + */ +_TLIBC_DEPRECATED_FUNCTION_(int _TLIBC_CDECL_, execl, const char *, const char *, ...); +_TLIBC_DEPRECATED_FUNCTION_(int _TLIBC_CDECL_, execlp, const char *, const char *, ...); +_TLIBC_DEPRECATED_FUNCTION_(int _TLIBC_CDECL_, execle, const char *, const char *, ...); +_TLIBC_DEPRECATED_FUNCTION_(int _TLIBC_CDECL_, execv, const char *, char * const *); +_TLIBC_DEPRECATED_FUNCTION_(int _TLIBC_CDECL_, execve, const char *, char * const *, char * const *); +_TLIBC_DEPRECATED_FUNCTION_(int _TLIBC_CDECL_, execvp, const char *, char * const *); + +//_TLIBC_DEPRECATED_FUNCTION_(pid_t _TLIBC_CDECL_, fork, void); /* no pid_t */ + +__END_DECLS + +#endif /* !_UNISTD_H_ */ diff --git a/tee-worker/rust-sgx-sdk/common/inc/wchar.h b/tee-worker/rust-sgx-sdk/common/inc/wchar.h new file mode 100644 index 0000000000..2db86f28eb --- /dev/null +++ b/tee-worker/rust-sgx-sdk/common/inc/wchar.h @@ -0,0 +1,143 @@ +/* $OpenBSD: wchar.h,v 1.11 2010/07/24 09:58:39 guenther Exp $ */ +/* $NetBSD: wchar.h,v 1.16 2003/03/07 07:11:35 tshiozak Exp $ */ + +/*- + * Copyright (c)1999 Citrus Project, + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +/*- + * Copyright (c) 1999, 2000 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by Julian Coleman. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef _WCHAR_H_ +#define _WCHAR_H_ + +#include +#include +#include /* WCHAR_MAX/WCHAR_MIN */ + +#ifndef NULL +#ifdef __cplusplus +#define NULL 0 +#else +#define NULL ((void *)0) +#endif +#endif + +#if !defined(_WCHAR_T_DEFINED_) && !defined(__cplusplus) +#define _WCHAR_T_DEFINED_ +#ifndef __WCHAR_TYPE__ +#define __WCHAR_TYPE__ int +#endif +typedef __WCHAR_TYPE__ wchar_t; +#endif + +#ifndef _MBSTATE_T_DEFINED_ +#define _MBSTATE_T_DEFINED_ +typedef __mbstate_t mbstate_t; +#endif + +#ifndef _WINT_T_DEFINED_ +#define _WINT_T_DEFINED_ +typedef __wint_t wint_t; +#endif + +#ifndef _SIZE_T_DEFINED_ +#define _SIZE_T_DEFINED_ +typedef __size_t size_t; +#endif + +#ifndef WEOF +#define WEOF ((wint_t)-1) +#endif + +__BEGIN_DECLS + +wint_t _TLIBC_CDECL_ btowc(int); +int _TLIBC_CDECL_ wctob(wint_t); +size_t _TLIBC_CDECL_ mbrlen(const char *, size_t, mbstate_t *); +size_t _TLIBC_CDECL_ mbrtowc(wchar_t *, const char *, size_t, mbstate_t *); +int _TLIBC_CDECL_ mbsinit(const mbstate_t *); +size_t _TLIBC_CDECL_ mbsrtowcs(wchar_t *, const char **, size_t, mbstate_t *); +size_t _TLIBC_CDECL_ wcrtomb(char *, wchar_t, mbstate_t *); +wchar_t * _TLIBC_CDECL_ wcschr(const wchar_t *, wchar_t); +int _TLIBC_CDECL_ wcscmp(const wchar_t *, const wchar_t *); +int _TLIBC_CDECL_ wcscoll(const wchar_t *, const wchar_t *); +size_t _TLIBC_CDECL_ wcscspn(const wchar_t *, const wchar_t *); +size_t _TLIBC_CDECL_ wcslen(const wchar_t *); +wchar_t * _TLIBC_CDECL_ wcsncat(wchar_t *, const wchar_t *, size_t); +int _TLIBC_CDECL_ wcsncmp(const wchar_t *, const wchar_t *, size_t); +wchar_t * _TLIBC_CDECL_ wcsncpy(wchar_t *, const wchar_t *, size_t); +wchar_t * _TLIBC_CDECL_ wcspbrk(const wchar_t *, const wchar_t *); +wchar_t * _TLIBC_CDECL_ wcsrchr(const wchar_t *, wchar_t); +size_t _TLIBC_CDECL_ wcsrtombs(char *, const wchar_t **, size_t, mbstate_t *); +size_t _TLIBC_CDECL_ wcsspn(const wchar_t *, const wchar_t *); +wchar_t * _TLIBC_CDECL_ wcsstr(const wchar_t *, const wchar_t *); +wchar_t * _TLIBC_CDECL_ wcstok(wchar_t *, const wchar_t *, wchar_t **); +size_t _TLIBC_CDECL_ wcsxfrm(wchar_t *, const wchar_t *, size_t); +wchar_t * _TLIBC_CDECL_ wmemchr(const wchar_t *, wchar_t, size_t); +int _TLIBC_CDECL_ wmemcmp(const wchar_t *, const wchar_t *, size_t); +wchar_t * _TLIBC_CDECL_ wmemcpy(wchar_t *, const wchar_t *, size_t); +wchar_t * _TLIBC_CDECL_ wmemmove(wchar_t *, const wchar_t *, size_t); +wchar_t * _TLIBC_CDECL_ wmemset(wchar_t *, wchar_t, size_t); + +int _TLIBC_CDECL_ swprintf(wchar_t *, size_t, const wchar_t *, ...); +int _TLIBC_CDECL_ vswprintf(wchar_t *, size_t, const wchar_t *, __va_list); + +long double _TLIBC_CDECL_ wcstold (const wchar_t *, wchar_t **); +long long _TLIBC_CDECL_ wcstoll (const wchar_t *, wchar_t **, int); +unsigned long long _TLIBC_CDECL_ wcstoull (const wchar_t *, wchar_t **, int); + +/* leagcy version of wcsstr */ +wchar_t * _TLIBC_CDECL_ wcswcs(const wchar_t *, const wchar_t *); + +__END_DECLS + +#endif /* !_WCHAR_H_ */ diff --git a/tee-worker/rust-sgx-sdk/common/inc/wctype.h b/tee-worker/rust-sgx-sdk/common/inc/wctype.h new file mode 100644 index 0000000000..0ab9497d78 --- /dev/null +++ b/tee-worker/rust-sgx-sdk/common/inc/wctype.h @@ -0,0 +1,80 @@ +/* $OpenBSD: wctype.h,v 1.5 2006/01/06 18:53:04 millert Exp $ */ +/* $NetBSD: wctype.h,v 1.5 2003/03/02 22:18:11 tshiozak Exp $ */ + +/*- + * Copyright (c)1999 Citrus Project, + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * citrus Id: wctype.h,v 1.4 2000/12/21 01:50:21 itojun Exp + */ + +#ifndef _WCTYPE_H_ +#define _WCTYPE_H_ + +#include +#include + +#ifndef _WINT_T_DEFINED_ +#define _WINT_T_DEFINED_ +typedef __wint_t wint_t; +#endif + +#ifndef _WCTRANS_T_DEFINED_ +#define _WCTRANS_T_DEFINED_ +typedef __wctrans_t wctrans_t; +#endif + +#ifndef _WCTYPE_T_DEFINED_ +#define _WCTYPE_T_DEFINED_ +typedef __wctype_t wctype_t; +#endif + +#ifndef WEOF +#define WEOF ((wint_t)-1) +#endif + +__BEGIN_DECLS + +int _TLIBC_CDECL_ iswalnum(wint_t); +int _TLIBC_CDECL_ iswalpha(wint_t); +int _TLIBC_CDECL_ iswblank(wint_t); +int _TLIBC_CDECL_ iswcntrl(wint_t); +int _TLIBC_CDECL_ iswdigit(wint_t); +int _TLIBC_CDECL_ iswgraph(wint_t); +int _TLIBC_CDECL_ iswlower(wint_t); +int _TLIBC_CDECL_ iswprint(wint_t); +int _TLIBC_CDECL_ iswpunct(wint_t); +int _TLIBC_CDECL_ iswspace(wint_t); +int _TLIBC_CDECL_ iswupper(wint_t); +int _TLIBC_CDECL_ iswxdigit(wint_t); +int _TLIBC_CDECL_ iswctype(wint_t, wctype_t); +wint_t _TLIBC_CDECL_ towctrans(wint_t, wctrans_t); +wint_t _TLIBC_CDECL_ towlower(wint_t); +wint_t _TLIBC_CDECL_ towupper(wint_t); +wctrans_t _TLIBC_CDECL_ wctrans(const char *); +wctype_t _TLIBC_CDECL_ wctype(const char *); + +__END_DECLS + +#endif /* _WCTYPE_H_ */ diff --git a/tee-worker/rust-sgx-sdk/edl/inc/dirent.h b/tee-worker/rust-sgx-sdk/edl/inc/dirent.h new file mode 100644 index 0000000000..be63f8332d --- /dev/null +++ b/tee-worker/rust-sgx-sdk/edl/inc/dirent.h @@ -0,0 +1,39 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License.. + +#ifndef _EDL_DIRENT_H +#define _EDL_DIRENT_H + +struct dirent_t +{ + uint64_t d_ino; + int64_t d_off; + unsigned short int d_reclen; + unsigned char d_type; + char d_name[256]; +}; + +struct dirent64_t +{ + uint64_t d_ino; + int64_t d_off; + unsigned short int d_reclen; + unsigned char d_type; + char d_name[256]; +}; + +#endif diff --git a/tee-worker/rust-sgx-sdk/edl/inc/stat.h b/tee-worker/rust-sgx-sdk/edl/inc/stat.h new file mode 100644 index 0000000000..7f04c3cec9 --- /dev/null +++ b/tee-worker/rust-sgx-sdk/edl/inc/stat.h @@ -0,0 +1,65 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License.. + +#ifndef _EDL_STAT_H +#define _EDL_STAT_H + +struct stat_t +{ + uint64_t st_dev; + uint64_t st_ino; + uint64_t st_nlink; + uint32_t st_mode; + uint32_t st_uid; + uint32_t st_gid; + int __pad0; + uint64_t st_rdev; + uint64_t st_size; + int64_t st_blksize; + int64_t st_blocks; + int64_t st_atime; + int64_t st_atime_nsec; + int64_t st_mtime; + int64_t st_mtime_nsec; + int64_t st_ctime; + int64_t st_ctime_nsec; + int64_t __reserved[3]; +}; + +struct stat64_t +{ + uint64_t st_dev; + uint64_t st_ino; + uint64_t st_nlink; + uint32_t st_mode; + uint32_t st_uid; + uint32_t st_gid; + int __pad0; + uint64_t st_rdev; + uint64_t st_size; + int64_t st_blksize; + int64_t st_blocks; + int64_t st_atime; + int64_t st_atime_nsec; + int64_t st_mtime; + int64_t st_mtime_nsec; + int64_t st_ctime; + int64_t st_ctime_nsec; + int64_t __reserved[3]; +}; + +#endif diff --git a/tee-worker/rust-sgx-sdk/edl/intel/sgx_dcap_tvl.edl b/tee-worker/rust-sgx-sdk/edl/intel/sgx_dcap_tvl.edl new file mode 100644 index 0000000000..7c5c0d8c69 --- /dev/null +++ b/tee-worker/rust-sgx-sdk/edl/intel/sgx_dcap_tvl.edl @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2011-2020 Intel Corporation. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +enclave { + + include "sgx_qve_header.h" + include "sgx_ql_quote.h" + + + trusted { + + /** + * Verify QvE Report and Identity + * + * @param p_quote[IN] - Pointer to SGX Quote. + * @param quote_size[IN] - Size of the buffer pointed to by p_quote (in bytes). + * @param p_qve_report_info[IN] - The output of API "sgx_qv_verify_quote", it should contain QvE report and nonce + * @param expiration_check_date[IN] - This is the date to verify QvE report data, you should use same value for this API and "sgx_qv_verify_quote" + * @param collateral_expiration_status[IN] - The output of API "sgx_qv_verify_quote" about quote verification collateral's expiration status + * @param quote_verification_result[IN] - The output of API "sgx_qv_verify_quote" about quote verification result + * @param p_supplemental_data[IN] - The output of API "sgx_qv_verify_quote", the pointer to supplemental data + * @param supplemental_data_size[IN] - Size of the buffer pointed to by p_quote (in bytes) + * @param qve_isvsvn_threshold [IN] - The threshold of QvE ISVSVN, the ISVSVN of QvE used to verify quote must be greater or equal to this threshold. You can get latest QvE ISVSVN in QvE Identity (JSON) from Intel PCS. + * + * @return Status code of the operation, one of: + * - SGX_QL_SUCCESS + * - SGX_QL_ERROR_INVALID_PARAMETER + * - SGX_QL_ERROR_REPORT // Error when verifying QvE report + * - SGX_QL_ERROR_UNEXPECTED // Error when comparing QvE report data + * - SGX_QL_QVEIDENTITY_MISMATCH // Error when comparing QvE identity + * - SGX_QL_QVE_OUT_OF_DATE // QvE ISVSVN is smaller than input QvE ISV SVN threshold + **/ + + public quote3_error_t sgx_tvl_verify_qve_report_and_identity( + [in, size=quote_size] const uint8_t *p_quote, + uint32_t quote_size, + [in, count=1] const sgx_ql_qe_report_info_t *p_qve_report_info, + time_t expiration_check_date, + uint32_t collateral_expiration_status, + sgx_ql_qv_result_t quote_verification_result, + [in, size=supplemental_data_size] const uint8_t *p_supplemental_data, + uint32_t supplemental_data_size, + sgx_isv_svn_t qve_isvsvn_threshold); + }; +}; diff --git a/tee-worker/rust-sgx-sdk/edl/intel/sgx_pthread.edl b/tee-worker/rust-sgx-sdk/edl/intel/sgx_pthread.edl new file mode 100644 index 0000000000..7a097a7396 --- /dev/null +++ b/tee-worker/rust-sgx-sdk/edl/intel/sgx_pthread.edl @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2011-2019 Intel Corporation. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +enclave { + untrusted { + [cdecl] int pthread_wait_timeout_ocall (unsigned long long waiter, unsigned long long timeout); + [cdecl] int pthread_create_ocall(unsigned long long self); + [cdecl] int pthread_wakeup_ocall(unsigned long long waiter); + }; +}; diff --git a/tee-worker/rust-sgx-sdk/edl/intel/sgx_tkey_exchange.edl b/tee-worker/rust-sgx-sdk/edl/intel/sgx_tkey_exchange.edl new file mode 100644 index 0000000000..3e18c89582 --- /dev/null +++ b/tee-worker/rust-sgx-sdk/edl/intel/sgx_tkey_exchange.edl @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2011-2019 Intel Corporation. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +enclave { + trusted { + public sgx_status_t sgx_ra_get_ga(sgx_ra_context_t context, + [out] sgx_ec256_public_t *g_a); + + public sgx_status_t sgx_ra_proc_msg2_trusted(sgx_ra_context_t context, + [in]const sgx_ra_msg2_t *p_msg2, /*copy msg2 except quote into enclave */ + [in] const sgx_target_info_t *p_qe_target, + [out] sgx_report_t *p_report, + [out] sgx_quote_nonce_t *p_nonce); + + public sgx_status_t sgx_ra_get_msg3_trusted(sgx_ra_context_t context, + uint32_t quote_size, + [in]sgx_report_t* qe_report, + [user_check]sgx_ra_msg3_t *p_msg3, + uint32_t msg3_size); + }; +}; diff --git a/tee-worker/rust-sgx-sdk/edl/intel/sgx_tprotected_fs.edl b/tee-worker/rust-sgx-sdk/edl/intel/sgx_tprotected_fs.edl new file mode 100644 index 0000000000..2dfad370a9 --- /dev/null +++ b/tee-worker/rust-sgx-sdk/edl/intel/sgx_tprotected_fs.edl @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2011-2019 Intel Corporation. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +enclave { + from "sgx_tstdc.edl" import *; + untrusted { + void* u_sgxprotectedfs_exclusive_file_open([in, string] const char* filename, uint8_t read_only, [out] int64_t* file_size, [out] int32_t* error_code); + uint8_t u_sgxprotectedfs_check_if_file_exists([in, string] const char* filename); + int32_t u_sgxprotectedfs_fread_node([user_check] void* f, uint64_t node_number, [out, size=node_size] uint8_t* buffer, uint32_t node_size); + int32_t u_sgxprotectedfs_fwrite_node([user_check] void* f, uint64_t node_number, [in, size=node_size] uint8_t* buffer, uint32_t node_size); + int32_t u_sgxprotectedfs_fclose([user_check] void* f); + uint8_t u_sgxprotectedfs_fflush([user_check] void* f); + int32_t u_sgxprotectedfs_remove([in, string] const char* filename); + + void* u_sgxprotectedfs_recovery_file_open([in, string] const char* filename); + uint8_t u_sgxprotectedfs_fwrite_recovery_node([user_check] void* f, [in, count=data_length] uint8_t* data, uint32_t data_length); + int32_t u_sgxprotectedfs_do_file_recovery([in, string] const char* filename, [in, string] const char* recovery_filename, uint32_t node_size); + }; +}; diff --git a/tee-worker/rust-sgx-sdk/edl/intel/sgx_tstdc.edl b/tee-worker/rust-sgx-sdk/edl/intel/sgx_tstdc.edl new file mode 100644 index 0000000000..4124debcfb --- /dev/null +++ b/tee-worker/rust-sgx-sdk/edl/intel/sgx_tstdc.edl @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2011-2019 Intel Corporation. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +enclave { + untrusted { + [cdecl] void sgx_oc_cpuidex([out] int cpuinfo[4], int leaf, int subleaf); + + /* Go outside and wait on my untrusted event */ + [cdecl] int sgx_thread_wait_untrusted_event_ocall([user_check] const void *self); + + /* Wake a thread waiting on its untrusted event */ + [cdecl] int sgx_thread_set_untrusted_event_ocall([user_check] const void *waiter); + + /* Wake a thread waiting on its untrusted event, and wait on my untrusted event */ + [cdecl] int sgx_thread_setwait_untrusted_events_ocall([user_check] const void *waiter, [user_check] const void *self); + + /* Wake multiple threads waiting on their untrusted events */ + [cdecl] int sgx_thread_set_multiple_untrusted_events_ocall([in, count = total] const void **waiters, size_t total); + }; +}; diff --git a/tee-worker/rust-sgx-sdk/edl/intel/sgx_tswitchless.edl b/tee-worker/rust-sgx-sdk/edl/intel/sgx_tswitchless.edl new file mode 100644 index 0000000000..a20669ab59 --- /dev/null +++ b/tee-worker/rust-sgx-sdk/edl/intel/sgx_tswitchless.edl @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2011-2019 Intel Corporation. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +enclave { + + trusted { + public sgx_status_t sl_init_switchless([user_check]void* sl_data); + public sgx_status_t sl_run_switchless_tworker(); + }; + +}; diff --git a/tee-worker/rust-sgx-sdk/edl/intel/sgx_ttls.edl b/tee-worker/rust-sgx-sdk/edl/intel/sgx_ttls.edl new file mode 100644 index 0000000000..ca0906f578 --- /dev/null +++ b/tee-worker/rust-sgx-sdk/edl/intel/sgx_ttls.edl @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2011-2021 Intel Corporation. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +enclave{ + include "sgx_report.h" + include "sgx_qve_header.h" + include "sgx_ql_lib_common.h" + include "sgx_ql_quote.h" + + untrusted { + quote3_error_t sgx_tls_get_qe_target_info_ocall([size = target_info_size, out] sgx_target_info_t *p_target_info, + size_t target_info_size); + + quote3_error_t sgx_tls_get_quote_size_ocall([out] uint32_t *p_quote_size); + + quote3_error_t sgx_tls_get_quote_ocall([size = report_size, in] sgx_report_t* p_report, + size_t report_size, + [size = quote_size, out] uint8_t *p_quote, + uint32_t quote_size); + + quote3_error_t sgx_tls_get_supplemental_data_size_ocall([out] uint32_t *p_supplemental_data_size); + + quote3_error_t sgx_tls_verify_quote_ocall( + [size = quote_size, in] const uint8_t *p_quote, + uint32_t quote_size, + time_t expiration_check_date, + [out] sgx_ql_qv_result_t *p_quote_verification_result, + [size = qve_report_info_size, in, out] sgx_ql_qe_report_info_t *p_qve_report_info, + size_t qve_report_info_size, + [size = supplemental_data_size, out] uint8_t *p_supplemental_data, + uint32_t supplemental_data_size); + + }; +}; diff --git a/tee-worker/rust-sgx-sdk/edl/sgx_asyncio.edl b/tee-worker/rust-sgx-sdk/edl/sgx_asyncio.edl new file mode 100644 index 0000000000..f46373894e --- /dev/null +++ b/tee-worker/rust-sgx-sdk/edl/sgx_asyncio.edl @@ -0,0 +1,33 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +enclave { + + include "sys/epoll.h" + include "poll.h" + + trusted { + /* define ECALLs here. */ + }; + + untrusted { + int u_poll_ocall([out] int *error, [in, out, count=nfds] struct pollfd *fds, nfds_t nfds, int timeout); + int u_epoll_create1_ocall([out] int *error, int flags); + int u_epoll_ctl_ocall([out] int *error, int epfd, int op, int fd, [in] struct epoll_event *event); + int u_epoll_wait_ocall([out] int *error, int epfd, [out, count=maxevents] struct epoll_event *events, int maxevents, int timeout); + }; +}; diff --git a/tee-worker/rust-sgx-sdk/edl/sgx_backtrace.edl b/tee-worker/rust-sgx-sdk/edl/sgx_backtrace.edl new file mode 100644 index 0000000000..4a9e7ef8c4 --- /dev/null +++ b/tee-worker/rust-sgx-sdk/edl/sgx_backtrace.edl @@ -0,0 +1,31 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +enclave { + + from "sgx_fd.edl" import *; + from "sgx_file.edl" import *; + from "sgx_mem.edl" import *; + + trusted { + /* define ECALLs here. */ + }; + + untrusted { + /* define OCALLs here. */ + }; +}; diff --git a/tee-worker/rust-sgx-sdk/edl/sgx_env.edl b/tee-worker/rust-sgx-sdk/edl/sgx_env.edl new file mode 100644 index 0000000000..d4a77cc816 --- /dev/null +++ b/tee-worker/rust-sgx-sdk/edl/sgx_env.edl @@ -0,0 +1,40 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +enclave { + + include "pwd.h" + + trusted { + /* define ECALLs here. */ + }; + + untrusted { + char **u_environ_ocall(); + char *u_getenv_ocall([in, string] const char *name); + int u_setenv_ocall([out] int *error, [in, string] const char *name, [in, string] const char *value, int overwrite); + int u_unsetenv_ocall([out] int *error, [in, string] const char *name); + int u_chdir_ocall([out] int *error, [in, string] const char *dir); + char *u_getcwd_ocall([out] int *error, [out, size=buflen] char *buf, size_t buflen); + int u_getpwuid_r_ocall(unsigned int uid, + [out] struct passwd *pwd, + [out, size=buflen] char *buf, + size_t buflen, + [out] struct passwd **passwd_result); + unsigned int u_getuid_ocall(); + }; +}; diff --git a/tee-worker/rust-sgx-sdk/edl/sgx_fd.edl b/tee-worker/rust-sgx-sdk/edl/sgx_fd.edl new file mode 100644 index 0000000000..cd668b71c0 --- /dev/null +++ b/tee-worker/rust-sgx-sdk/edl/sgx_fd.edl @@ -0,0 +1,57 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +enclave { + + include "inc/stat.h" + include "sys/uio.h" + include "time.h" + + from "sgx_mem.edl" import *; + + trusted { + /* define ECALLs here. */ + }; + + untrusted { + size_t u_read_ocall([out] int *error, int fd, [user_check] void *buf, size_t count); + size_t u_pread64_ocall([out] int *error, int fd, [user_check] void *buf, size_t count, int64_t offset); + size_t u_readv_ocall([out] int *error, int fd, [in, count=iovcnt] const struct iovec *iov, int iovcnt); + size_t u_preadv64_ocall([out] int *error, int fd, [in, count=iovcnt] const struct iovec *iov, int iovcnt, int64_t offset); + + size_t u_write_ocall([out] int *error, int fd, [user_check] const void *buf, size_t count); + size_t u_pwrite64_ocall([out] int *error, int fd, [user_check] const void *buf, size_t count, int64_t offset); + size_t u_writev_ocall([out] int *error, int fd, [in, count=iovcnt] const struct iovec *iov, int iovcnt); + size_t u_pwritev64_ocall([out] int *error, int fd, [in, count=iovcnt] const struct iovec *iov, int iovcnt, int64_t offset); + + size_t u_sendfile_ocall([out] int *error, int out_fd, int in_fd, [in, out] int64_t *offset, size_t count); + size_t u_copy_file_range_ocall([out] int *error, int fd_in, [in, out] int64_t *off_in, int fd_out, [in, out] int64_t *off_out, size_t len, unsigned int flags); + size_t u_splice_ocall([out] int *error, int fd_in, [in, out] int64_t *off_in, int fd_out, [in, out] int64_t *off_out, size_t len, unsigned int flags); + + int u_fcntl_arg0_ocall([out] int *error, int fd, int cmd); + int u_fcntl_arg1_ocall([out] int *error, int fd, int cmd, int arg); + int u_ioctl_arg0_ocall([out] int *error, int fd, int request); + int u_ioctl_arg1_ocall([out] int *error, int fd, int request, [in, out] int *arg); + + int u_close_ocall([out] int *error, int fd); + int u_isatty_ocall([out] int *error, int fd); + int u_dup_ocall([out] int *error, int oldfd); + int u_eventfd_ocall([out] int *error, unsigned int initval, int flags); + + int u_futimens_ocall([out] int *error, int fd, [in, count=2] const struct timespec *times); + }; +}; diff --git a/tee-worker/rust-sgx-sdk/edl/sgx_file.edl b/tee-worker/rust-sgx-sdk/edl/sgx_file.edl new file mode 100644 index 0000000000..c70ec599a2 --- /dev/null +++ b/tee-worker/rust-sgx-sdk/edl/sgx_file.edl @@ -0,0 +1,66 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +enclave { + + include "inc/stat.h" + include "inc/dirent.h" + + trusted { + /* define ECALLs here. */ + }; + + untrusted { + int u_open_ocall([out] int *error, [in, string] const char *pathname, int flags); + int u_open64_ocall([out] int *error, [in, string] const char *path, int oflag, int mode); + int u_openat_ocall([out] int *error, int dirfd, [in, string] const char *pathname, int flags); + + int u_fstat_ocall([out] int *error, int fd, [out] struct stat_t *buf); + int u_fstat64_ocall([out] int *error, int fd, [out] struct stat64_t *buf); + int u_stat_ocall([out] int *error, [in, string] const char *path, [out] struct stat_t *buf); + int u_stat64_ocall([out] int *error, [in, string] const char *path, [out] struct stat64_t *buf); + int u_lstat_ocall([out] int *error, [in, string] const char *path, [out] struct stat_t *buf); + int u_lstat64_ocall([out] int *error, [in, string] const char *path, [out] struct stat64_t *buf); + uint64_t u_lseek_ocall([out] int *error, int fd, int64_t offset, int whence); + int64_t u_lseek64_ocall([out] int *error, int fd, int64_t offset, int whence); + int u_ftruncate_ocall([out] int *error, int fd, int64_t length); + int u_ftruncate64_ocall([out] int *error, int fd, int64_t length); + int u_truncate_ocall([out] int *error, [in, string] const char *path, int64_t length); + int u_truncate64_ocall([out] int *error, [in, string] const char *path, int64_t length); + + int u_fsync_ocall([out] int *error, int fd); + int u_fdatasync_ocall([out] int *error, int fd); + int u_fchmod_ocall([out] int *error, int fd, uint32_t mode); + int u_unlink_ocall([out] int *error, [in, string] const char *pathname); + int u_link_ocall([out] int *error, [in, string] const char *oldpath, [in, string] const char *newpath); + int u_unlinkat_ocall([out] int *error, int dirfd, [in, string] const char *pathname, int flags); + int u_linkat_ocall([out] int *error, int olddirfd, [in, string] const char *oldpath, int newdirfd, [in, string] const char *newpath, int flags); + int u_rename_ocall([out] int *error, [in, string] const char *oldpath, [in, string] const char *newpath); + int u_chmod_ocall([out] int *error, [in, string] const char *path, uint32_t mode); + size_t u_readlink_ocall([out] int *error, [in, string] const char *path, [out, size=bufsz] char *buf, size_t bufsz); + int u_symlink_ocall([out] int *error, [in, string] const char *path1, [in, string] const char *path2); + char *u_realpath_ocall([out] int *error, [in, string] const char *pathname); + int u_mkdir_ocall([out] int *error, [in, string] const char *pathname, uint32_t mode); + int u_rmdir_ocall([out] int *error, [in, string] const char *pathname); + void *u_fdopendir_ocall([out] int *error, int fd); + void *u_opendir_ocall([out] int *error, [in, string] const char *pathname); + int u_readdir64_r_ocall([user_check] void *dirp, [in, out] struct dirent64_t *entry, [out] struct dirent64_t **result); + int u_closedir_ocall([out] int *error, [user_check] void *dirp); + int u_dirfd_ocall([out] int *error, [user_check] void *dirp); + int u_fstatat64_ocall([out] int *error, int dirfd, [in, string] const char *pathname, [out] struct stat64_t *buf, int flags); + }; +}; diff --git a/tee-worker/rust-sgx-sdk/edl/sgx_fs.edl b/tee-worker/rust-sgx-sdk/edl/sgx_fs.edl new file mode 100644 index 0000000000..2618be9352 --- /dev/null +++ b/tee-worker/rust-sgx-sdk/edl/sgx_fs.edl @@ -0,0 +1,31 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +enclave { + + from "sgx_mem.edl" import *; + from "sgx_fd.edl" import *; + from "sgx_file.edl" import *; + + trusted { + /* define ECALLs here. */ + }; + + untrusted { + /* define OCALLs here. */ + }; +}; diff --git a/tee-worker/rust-sgx-sdk/edl/sgx_mem.edl b/tee-worker/rust-sgx-sdk/edl/sgx_mem.edl new file mode 100644 index 0000000000..db55802755 --- /dev/null +++ b/tee-worker/rust-sgx-sdk/edl/sgx_mem.edl @@ -0,0 +1,40 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +enclave { + + trusted { + /* define ECALLs here. */ + }; + + untrusted { + void *u_malloc_ocall([out] int *error, size_t size); + void u_free_ocall([user_check] void *p); + + void *u_mmap_ocall([out] int *error, + [user_check] void *start, + size_t length, + int prot, + int flags, + int fd, + int64_t offset); + int u_munmap_ocall([out] int *error, [user_check] void *start, size_t length); + + int u_msync_ocall([out] int *error, [user_check] void *addr, size_t length, int flags); + int u_mprotect_ocall([out] int *error, [user_check] void *addr, size_t length, int prot); + }; +}; diff --git a/tee-worker/rust-sgx-sdk/edl/sgx_net.edl b/tee-worker/rust-sgx-sdk/edl/sgx_net.edl new file mode 100644 index 0000000000..a803b53ac2 --- /dev/null +++ b/tee-worker/rust-sgx-sdk/edl/sgx_net.edl @@ -0,0 +1,41 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +enclave { + + include "sys/socket.h" + include "netdb.h" + + from "sgx_socket.edl" import *; + from "sgx_asyncio.edl" import *; + from "sgx_fd.edl" import *; + from "sgx_time.edl" import *; + + trusted { + /* define ECALLs here. */ + }; + + untrusted { + int u_getaddrinfo_ocall([out] int *error, + [in, string] const char *node, + [in, string] const char *service, + [in] const struct addrinfo *hints, + [out] struct addrinfo **res); + void u_freeaddrinfo_ocall([user_check] struct addrinfo *res); + char *u_gai_strerror_ocall(int errcode); + }; +}; diff --git a/tee-worker/rust-sgx-sdk/edl/sgx_net_switchless.edl b/tee-worker/rust-sgx-sdk/edl/sgx_net_switchless.edl new file mode 100644 index 0000000000..ec5c500cfc --- /dev/null +++ b/tee-worker/rust-sgx-sdk/edl/sgx_net_switchless.edl @@ -0,0 +1,92 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +enclave { + + include "sys/socket.h" + include "poll.h" + from "sgx_fs.edl" import *; + from "sgx_time.edl" import *; + from "sgx_mem.edl" import *; + + trusted { + /* define ECALLs here. */ + }; + + untrusted { + + int u_net_socket_ocall([out] int *error, int domain, int ty, int protocol) transition_using_threads; + int u_net_socketpair_ocall([out] int *error, int domain, int ty, int protocol, [out] int sv[2]) transition_using_threads; + int u_net_bind_ocall([out] int *error, int sockfd, [in, size=addrlen] const struct sockaddr *addr, socklen_t addrlen) transition_using_threads; + int u_net_listen_ocall([out] int *error, int sockfd, int backlog) transition_using_threads; + int u_net_accept4_ocall([out] int *error, + int sockfd, + [in, out, size=addrlen_in] struct sockaddr *addr, + socklen_t addrlen_in, + [out] socklen_t *addrlen_out, + int flags) transition_using_threads; + int u_net_connect_ocall([out] int *error, + int sockfd, + [in, size=addrlen] const struct sockaddr *addr, + socklen_t addrlen) transition_using_threads; + size_t u_net_recv_ocall([out] int *error, int sockfd, [out, size=len] void *buf, size_t len, int flags) transition_using_threads; + size_t u_net_recvfrom_ocall([out] int *error, + int sockfd, + [out, size=len] void *buf, + size_t len, + int flags, + [out, size=addrlen_in] struct sockaddr *src_addr, + socklen_t addrlen_in, + [out] socklen_t *addrlen_out) transition_using_threads; + size_t u_net_recvmsg_ocall([out] int *error, int sockfd, [in, out] struct msghdr *msg, int flags) transition_using_threads; + size_t u_net_send_ocall([out] int *error, int sockfd, [in, size=len] const void *buf, size_t len, int flags) transition_using_threads; + size_t u_net_sendto_ocall([out] int *error, + int sockfd, + [in, size=len] const void *buf, + size_t len, + int flags, + [in, size=addrlen] const struct sockaddr *dest_addr, + socklen_t addrlen) transition_using_threads; + size_t u_sendmsg_ocall([out] int *error, int sockfd, [in] const struct msghdr *msg, int flags) transition_using_threads; + int u_net_getsockopt_ocall([out] int *error, + int sockfd, + int level, + int optname, + [out, size=optlen_in] void *optval, + socklen_t optlen_in, + [out] socklen_t *optlen_out) transition_using_threads; + int u_net_setsockopt_ocall([out] int *error, + int sockfd, + int level, + int optname, + [in, size=optlen] const void *optval, + socklen_t optlen) transition_using_threads; + int u_net_getsockname_ocall([out] int *error, + int sockfd, + [out, size=addrlen_in] struct sockaddr *addr, + socklen_t addrlen_in, + [out] socklen_t *addrlen_out) transition_using_threads; + int u_net_getpeername_ocall([out] int *error, + int sockfd, + [out, size=addrlen_in] struct sockaddr *addr, + socklen_t addrlen_in, + [out] socklen_t *addrlen_out) transition_using_threads; + int u_net_shutdown_ocall([out] int *error, int sockfd, int how) transition_using_threads; + int u_net_ioctl_ocall([out] int *error, int fd, int request, [in, out] int *arg) transition_using_threads; + int u_net_poll_ocall([out] int *error, [in, out, count=nfds] struct pollfd *fds, nfds_t nfds, int timeout) transition_using_threads; + }; +}; diff --git a/tee-worker/rust-sgx-sdk/edl/sgx_pipe.edl b/tee-worker/rust-sgx-sdk/edl/sgx_pipe.edl new file mode 100644 index 0000000000..00c12f5e7c --- /dev/null +++ b/tee-worker/rust-sgx-sdk/edl/sgx_pipe.edl @@ -0,0 +1,31 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +enclave { + + from "sgx_fd.edl" import *; + from "sgx_asyncio.edl" import *; + + trusted { + /* define ECALLs here. */ + }; + + untrusted { + int u_pipe_ocall([out] int *error, [out, count=2] int *pipefd); + int u_pipe2_ocall([out] int *error, [out, count=2] int *pipefd, int flags); + }; +}; diff --git a/tee-worker/rust-sgx-sdk/edl/sgx_process.edl b/tee-worker/rust-sgx-sdk/edl/sgx_process.edl new file mode 100644 index 0000000000..69123df5d8 --- /dev/null +++ b/tee-worker/rust-sgx-sdk/edl/sgx_process.edl @@ -0,0 +1,28 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +enclave { + + trusted { + /* define ECALLs here. */ + + }; + + untrusted { + int u_getpid_ocall(); + }; +}; diff --git a/tee-worker/rust-sgx-sdk/edl/sgx_signal.edl b/tee-worker/rust-sgx-sdk/edl/sgx_signal.edl new file mode 100644 index 0000000000..fd9b0f0d14 --- /dev/null +++ b/tee-worker/rust-sgx-sdk/edl/sgx_signal.edl @@ -0,0 +1,43 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +enclave { + include "signal.h" + + trusted { + /* define ECALLs here. */ + public int t_signal_handler_ecall([in]const siginfo_t *info); + }; + + untrusted { + int u_sigaction_ocall([out]int *error, + int signum, + [in] const struct sigaction *act, + [out] struct sigaction *oldact, + uint64_t enclave_id); + + int u_sigprocmask_ocall([out]int *error, + int signum, + [in] const sigset_t *set, + [out] sigset_t *oldset); + + int u_raise_ocall(int signum) allow(t_signal_handler_ecall); + + void u_signal_clear_ocall(uint64_t enclave_id); + }; +}; + diff --git a/tee-worker/rust-sgx-sdk/edl/sgx_socket.edl b/tee-worker/rust-sgx-sdk/edl/sgx_socket.edl new file mode 100644 index 0000000000..6fc8ff7c85 --- /dev/null +++ b/tee-worker/rust-sgx-sdk/edl/sgx_socket.edl @@ -0,0 +1,111 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +enclave { + + include "sys/socket.h" + + from "sgx_mem.edl" import *; + + trusted { + /* define ECALLs here. */ + }; + + untrusted { + int u_socket_ocall([out] int *error, int domain, int ty, int protocol); + int u_socketpair_ocall([out] int *error, int domain, int ty, int protocol, [out] int sv[2]); + int u_bind_ocall([out] int *error, int sockfd, [in, size=addrlen] const struct sockaddr *addr, socklen_t addrlen); + int u_listen_ocall([out] int *error, int sockfd, int backlog); + int u_accept_ocall([out] int *error, + int sockfd, + [in, out, size=addrlen_in] struct sockaddr *addr, + socklen_t addrlen_in, + [out] socklen_t *addrlen_out); + int u_accept4_ocall([out] int *error, + int sockfd, + [in, out, size=addrlen_in] struct sockaddr *addr, + socklen_t addrlen_in, + [out] socklen_t *addrlen_out, + int flags); + int u_connect_ocall([out] int *error, + int sockfd, + [in, size=addrlen] const struct sockaddr *addr, + socklen_t addrlen); + size_t u_recv_ocall([out] int *error, int sockfd,[user_check] void *buf, size_t len, int flags); + size_t u_recvfrom_ocall([out] int *error, + int sockfd, + [user_check] void *buf, + size_t len, + int flags, + [out, size=addrlen_in] struct sockaddr *src_addr, + socklen_t addrlen_in, + [out] socklen_t *addrlen_out); + size_t u_recvmsg_ocall([out] int *error, + int sockfd, + [out, size=msg_namelen] void *msg_name, + socklen_t msg_namelen, + [out] socklen_t* msg_namelen_out, + [in, count=msg_iovlen] struct iovec* msg_iov, + size_t msg_iovlen, + [out, size=msg_controllen] void *msg_control, + size_t msg_controllen, + [out] size_t* msg_controllen_out, + [out] int* msg_flags, + int flags); + size_t u_send_ocall([out] int *error, int sockfd, [user_check] const void *buf, size_t len, int flags); + size_t u_sendto_ocall([out] int *error, + int sockfd, + [user_check] const void *buf, + size_t len, + int flags, + [in, size=addrlen] const struct sockaddr *dest_addr, + socklen_t addrlen); + size_t u_sendmsg_ocall([out] int *error, + int sockfd, + [in, size=msg_namelen] const void* msg_name, + socklen_t msg_namelen, + [in, count=msg_iovlen] const struct iovec* msg_iov, + size_t msg_iovlen, + [in, size=msg_controllen] const void* msg_control, + size_t msg_controllen, + int flags); + int u_getsockopt_ocall([out] int *error, + int sockfd, + int level, + int optname, + [out, size=optlen_in] void *optval, + socklen_t optlen_in, + [out] socklen_t *optlen_out); + int u_setsockopt_ocall([out] int *error, + int sockfd, + int level, + int optname, + [in, size=optlen] const void *optval, + socklen_t optlen); + int u_getsockname_ocall([out] int *error, + int sockfd, + [out, size=addrlen_in] struct sockaddr *addr, + socklen_t addrlen_in, + [out] socklen_t *addrlen_out); + int u_getpeername_ocall([out] int *error, + int sockfd, + [out, size=addrlen_in] struct sockaddr *addr, + socklen_t addrlen_in, + [out] socklen_t *addrlen_out); + int u_shutdown_ocall([out] int *error, int sockfd, int how); + }; +}; diff --git a/tee-worker/rust-sgx-sdk/edl/sgx_stdio.edl b/tee-worker/rust-sgx-sdk/edl/sgx_stdio.edl new file mode 100644 index 0000000000..5367d9ab97 --- /dev/null +++ b/tee-worker/rust-sgx-sdk/edl/sgx_stdio.edl @@ -0,0 +1,29 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +enclave { + + from "sgx_fd.edl" import *; + + trusted { + /* define ECALLs here. */ + }; + + untrusted { + /* define OCALLs here. */ + }; +}; diff --git a/tee-worker/rust-sgx-sdk/edl/sgx_sys.edl b/tee-worker/rust-sgx-sdk/edl/sgx_sys.edl new file mode 100644 index 0000000000..bc74b96843 --- /dev/null +++ b/tee-worker/rust-sgx-sdk/edl/sgx_sys.edl @@ -0,0 +1,32 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +enclave { + + include "sched.h" + + trusted { + /* define ECALLs here. */ + }; + + untrusted { + long u_sysconf_ocall([out] int *error, int name); + int u_prctl_ocall([out] int *error, int option, unsigned long arg2, unsigned long arg3, unsigned long arg4, unsigned long arg5); + int u_sched_setaffinity_ocall([out] int *error, pid_t pid, size_t cpusetsize, [in, size=cpusetsize] cpu_set_t *mask); + int u_sched_getaffinity_ocall([out] int *error, pid_t pid, size_t cpusetsize, [out, size=cpusetsize] cpu_set_t *mask); + }; +}; diff --git a/tee-worker/rust-sgx-sdk/edl/sgx_thread.edl b/tee-worker/rust-sgx-sdk/edl/sgx_thread.edl new file mode 100644 index 0000000000..71512f0e56 --- /dev/null +++ b/tee-worker/rust-sgx-sdk/edl/sgx_thread.edl @@ -0,0 +1,32 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +enclave { + + include "time.h" + + from "intel/sgx_pthread.edl" import *; + from "sgx_sys.edl" import *; + + trusted { + /* define ECALLs here. */ + }; + + untrusted { + int u_sched_yield_ocall([out]int *error); + int u_nanosleep_ocall([out]int *error, [in]const struct timespec *req, [out]struct timespec *rem); + }; +}; diff --git a/tee-worker/rust-sgx-sdk/edl/sgx_time.edl b/tee-worker/rust-sgx-sdk/edl/sgx_time.edl new file mode 100644 index 0000000000..adeeeccf92 --- /dev/null +++ b/tee-worker/rust-sgx-sdk/edl/sgx_time.edl @@ -0,0 +1,29 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +enclave { + + include "time.h" + + trusted { + /* define ECALLs here. */ + }; + + untrusted { + int u_clock_gettime_ocall([out] int *error, int clk_id, [out] struct timespec *tp); + }; +}; diff --git a/tee-worker/rust-sgx-sdk/edl/sgx_tstd.edl b/tee-worker/rust-sgx-sdk/edl/sgx_tstd.edl new file mode 100644 index 0000000000..9b74272f50 --- /dev/null +++ b/tee-worker/rust-sgx-sdk/edl/sgx_tstd.edl @@ -0,0 +1,38 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +enclave { + + from "sgx_time.edl" import *; + + trusted { + /* define ECALLs here. */ + public void t_global_init_ecall(uint64_t id, [in, size=len] const uint8_t *path, size_t len); + public void t_global_exit_ecall(); + }; + + untrusted { + /* define OCALLs here. */ + int u_thread_set_event_ocall([out] int *error, [user_check] const void *tcs); + int u_thread_wait_event_ocall([out] int *error, [user_check] const void *tcs, [in] const struct timespec *timeout); + int u_thread_set_multiple_events_ocall([out] int *error, [in, count=total] const void **tcss, int total); + int u_thread_setwait_events_ocall([out] int *error, + [user_check] const void *waiter_tcs, + [user_check] const void *self_tcs, + [in] const struct timespec *timeout); + }; +}; diff --git a/tee-worker/rust-sgx-sdk/version b/tee-worker/rust-sgx-sdk/version new file mode 100644 index 0000000000..25740ac377 --- /dev/null +++ b/tee-worker/rust-sgx-sdk/version @@ -0,0 +1 @@ +9c1bbd52f188f600a212b57c916124245da1b7fd diff --git a/tee-worker/rust-toolchain.toml b/tee-worker/rust-toolchain.toml new file mode 100644 index 0000000000..23ed88e6c8 --- /dev/null +++ b/tee-worker/rust-toolchain.toml @@ -0,0 +1,4 @@ +[toolchain] +channel = "nightly-2022-10-22" +targets = ["wasm32-unknown-unknown"] +profile = "default" # include rustfmt, clippy diff --git a/tee-worker/rustfmt.toml b/tee-worker/rustfmt.toml new file mode 100644 index 0000000000..104b9aa998 --- /dev/null +++ b/tee-worker/rustfmt.toml @@ -0,0 +1,18 @@ +# Basic +hard_tabs = true +max_width = 100 +use_small_heuristics = "Max" +# Imports +imports_granularity = "Crate" +reorder_imports = true +# Consistency +newline_style = "Unix" +# Misc +chain_width = 80 +spaces_around_ranges = false +match_arm_leading_pipes = "Preserve" +match_arm_blocks = false +match_block_trailing_comma = true +trailing_comma = "Vertical" +trailing_semicolon = false +use_field_init_shorthand = true \ No newline at end of file diff --git a/tee-worker/scripts/benchmark_local-setup.sh b/tee-worker/scripts/benchmark_local-setup.sh new file mode 100644 index 0000000000..d5e1c462b2 --- /dev/null +++ b/tee-worker/scripts/benchmark_local-setup.sh @@ -0,0 +1,31 @@ +#!/bin/bash +set -e + +pushd .. + +pushd bin +./integritee-service mrenclave | tee ~/mrenclave.b58 +popd + +ulimit -S -n 4096 + +python3 local-setup/launch.py local-setup/benchmark-config.json & +PID=$! +echo $PID > ./benchmark.pid +echo "Benchmark PID: $PID" + +sleep 40s + +pushd bin +./integritee-cli -p 9930 -P 2030 trusted --direct --mrenclave "$(cat ~/mrenclave.b58)" benchmark 20 100 -w +popd + +sleep 10s + +if test -f "./benchmark.pid"; then + echo "Killing benchmark process" + kill -s SIGTERM "$(cat ./benchmark.pid)" + rm benchmark.pid +fi + +popd diff --git a/tee-worker/scripts/changelog/.gitignore b/tee-worker/scripts/changelog/.gitignore new file mode 100644 index 0000000000..4fbcc523b0 --- /dev/null +++ b/tee-worker/scripts/changelog/.gitignore @@ -0,0 +1,4 @@ +changelog.md +*.json +release*.md +.env diff --git a/tee-worker/scripts/changelog/Gemfile b/tee-worker/scripts/changelog/Gemfile new file mode 100644 index 0000000000..f2d7c3bd71 --- /dev/null +++ b/tee-worker/scripts/changelog/Gemfile @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +source 'https://rubygems.org' + +git_source(:github) { |repo_name| "https://github.com/#{repo_name}" } + +gem 'octokit', '~> 4' + +gem 'git_diff_parser', '~> 3' + +gem 'toml', '~> 0.3.0' + +gem 'rake', group: :dev + +gem 'optparse', '~> 0.1.1' + +gem 'logger', '~> 1.4' + +gem 'test-unit', group: :dev + +gem 'rubocop', group: :dev, require: false diff --git a/tee-worker/scripts/changelog/Gemfile.lock b/tee-worker/scripts/changelog/Gemfile.lock new file mode 100644 index 0000000000..855d7f91a5 --- /dev/null +++ b/tee-worker/scripts/changelog/Gemfile.lock @@ -0,0 +1,79 @@ +GEM + remote: https://rubygems.org/ + specs: + addressable (2.8.0) + public_suffix (>= 2.0.2, < 5.0) + ast (2.4.2) + faraday (1.8.0) + faraday-em_http (~> 1.0) + faraday-em_synchrony (~> 1.0) + faraday-excon (~> 1.1) + faraday-httpclient (~> 1.0.1) + faraday-net_http (~> 1.0) + faraday-net_http_persistent (~> 1.1) + faraday-patron (~> 1.0) + faraday-rack (~> 1.0) + multipart-post (>= 1.2, < 3) + ruby2_keywords (>= 0.0.4) + faraday-em_http (1.0.0) + faraday-em_synchrony (1.0.0) + faraday-excon (1.1.0) + faraday-httpclient (1.0.1) + faraday-net_http (1.0.1) + faraday-net_http_persistent (1.2.0) + faraday-patron (1.0.0) + faraday-rack (1.0.0) + git_diff_parser (3.2.0) + logger (1.4.4) + multipart-post (2.1.1) + octokit (4.21.0) + faraday (>= 0.9) + sawyer (~> 0.8.0, >= 0.5.3) + optparse (0.1.1) + parallel (1.21.0) + parser (3.0.2.0) + ast (~> 2.4.1) + parslet (2.0.0) + power_assert (2.0.1) + public_suffix (4.0.6) + rainbow (3.0.0) + rake (13.0.6) + regexp_parser (2.1.1) + rexml (3.2.5) + rubocop (1.23.0) + parallel (~> 1.10) + parser (>= 3.0.0.0) + rainbow (>= 2.2.2, < 4.0) + regexp_parser (>= 1.8, < 3.0) + rexml + rubocop-ast (>= 1.12.0, < 2.0) + ruby-progressbar (~> 1.7) + unicode-display_width (>= 1.4.0, < 3.0) + rubocop-ast (1.13.0) + parser (>= 3.0.1.1) + ruby-progressbar (1.11.0) + ruby2_keywords (0.0.5) + sawyer (0.8.2) + addressable (>= 2.3.5) + faraday (> 0.8, < 2.0) + test-unit (3.5.1) + power_assert + toml (0.3.0) + parslet (>= 1.8.0, < 3.0.0) + unicode-display_width (2.1.0) + +PLATFORMS + x86_64-darwin-20 + +DEPENDENCIES + git_diff_parser (~> 3) + logger (~> 1.4) + octokit (~> 4) + optparse (~> 0.1.1) + rake + rubocop + test-unit + toml (~> 0.3.0) + +BUNDLED WITH + 2.2.22 diff --git a/tee-worker/scripts/changelog/README.md b/tee-worker/scripts/changelog/README.md new file mode 100644 index 0000000000..4776277e70 --- /dev/null +++ b/tee-worker/scripts/changelog/README.md @@ -0,0 +1,3 @@ +## License + +Everything in this folder is GPL 3.0 licensed. The original has been authored by parity and was taken from here: https://github.com/paritytech/polkadot/tree/master/scripts/ci/changelog. \ No newline at end of file diff --git a/tee-worker/scripts/changelog/bin/changelog b/tee-worker/scripts/changelog/bin/changelog new file mode 100755 index 0000000000..15b17d6166 --- /dev/null +++ b/tee-worker/scripts/changelog/bin/changelog @@ -0,0 +1,84 @@ +#!/usr/bin/env ruby + +# frozen_string_literal: true + +# call for instance as: +# ./bin/changelog [] [] +# for instance, for the release notes of v1.2.3: +# ./bin/changelog v1.2.3 +# or +# ./bin/changelog v1.2.3 v1.2.2 +# +# You may set the ENV NO_CACHE to force fetching from Github +# You should also ensure you set the ENV: GITHUB_TOKEN + +require_relative '../lib/changelog' +require 'logger' + +logger = Logger.new($stdout) +logger.level = Logger::DEBUG +logger.debug('Starting') + +owner = 'integritee-network' +repo = 'worker' + +gh_worker = SubRef.new(format('%s/%s', { owner: owner, repo: repo })) +last_release_ref = gh_worker.get_last_ref() + +worker_ref2 = ARGV[0] || 'HEAD' +worker_ref1 = ARGV[1] || last_release_ref + +output = ARGV[2] || 'release-notes.md' + +ENV['REF1'] = worker_ref1 +ENV['REF2'] = worker_ref2 + +pallets_ref1 = gh_worker.get_dependency_reference(worker_ref1, 'pallet-teerex') +pallets_ref2 = gh_worker.get_dependency_reference(worker_ref2, 'pallet-teerex') + +logger.debug("Worker from: #{worker_ref1}") +logger.debug("Worker to: #{worker_ref2}") + +logger.debug("Pallets from: #{pallets_ref1}") +logger.debug("Pallets to: #{pallets_ref2}") + +pallets_data = 'pallets.json' +worker_data = 'worker.json' + +logger.debug("Using PALLETS: #{pallets_data}") +logger.debug("Using WORKER: #{worker_data}") + +logger.warn('NO_CACHE set') if ENV['NO_CACHE'] + +if ENV['NO_CACHE'] || !File.file?(worker_data) + logger.debug(format('Fetching data for Worker into %s', worker_data)) + cmd = format('changelogerator %s/%s -f %s -t %s > %s', + { owner: owner, repo: 'worker', from: worker_ref1, to: worker_ref2, output: worker_data }) + system(cmd) +else + logger.debug("Re-using:#{worker_data}") +end + +if ENV['NO_CACHE'] || !File.file?(pallets_data) + logger.debug(format('Fetching data for Pallets into %s', pallets_data)) + cmd = format('changelogerator %s/%s -f %s -t %s > %s', + { owner: owner, repo: 'pallets', from: pallets_ref1, to: pallets_ref2, output: pallets_data }) + system(cmd) +else + logger.debug("Re-using:#{pallets_data}") +end + +# Here we compose all the pieces together into one +# single big json file. +cmd = format('jq \ + --slurpfile pallets %s \ + --slurpfile worker %s \ + -n \'{ + pallets: $pallets[0], + worker: $worker[0], + }\' > context.json', pallets_data, worker_data) +system(cmd) + +cmd = format('tera --env --env-key env --include-path templates \ + --template templates/template.md.tera context.json > %s', output) +system(cmd) diff --git a/tee-worker/scripts/changelog/digests/.gitignore b/tee-worker/scripts/changelog/digests/.gitignore new file mode 100644 index 0000000000..a6c57f5fb2 --- /dev/null +++ b/tee-worker/scripts/changelog/digests/.gitignore @@ -0,0 +1 @@ +*.json diff --git a/tee-worker/scripts/changelog/digests/.gitkeep b/tee-worker/scripts/changelog/digests/.gitkeep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tee-worker/scripts/changelog/lib/changelog.rb b/tee-worker/scripts/changelog/lib/changelog.rb new file mode 100644 index 0000000000..d7cf92e7d2 --- /dev/null +++ b/tee-worker/scripts/changelog/lib/changelog.rb @@ -0,0 +1,38 @@ +# frozen_string_literal: true + +# A Class to find Substrate references +class SubRef + require 'octokit' + require 'toml' + + attr_reader :client, :repository + + def initialize(github_repo) + @client = Octokit::Client.new( + access_token: ENV['GITHUB_TOKEN'] + ) + @repository = @client.repository(github_repo) + end + + # This function checks the Cargo.lock of a given + # Rust project, for a given package, and fetches + # the dependency git ref. + def get_dependency_reference(ref, package) + cargo = TOML::Parser.new( + Base64.decode64( + @client.contents( + @repository.full_name, + path: 'Cargo.lock', + query: { ref: ref.to_s } + ).content + ) + ).parsed + cargo['package'].find { |p| p['name'] == package }['source'].split('#').last + end + + # Get the git ref of the last release for the repo. + # repo is given in the form integritee-network/worker + def get_last_ref() + 'refs/tags/' + @client.latest_release(@repository.full_name).tag_name + end +end diff --git a/tee-worker/scripts/changelog/templates/_free_notes.md.tera b/tee-worker/scripts/changelog/templates/_free_notes.md.tera new file mode 100644 index 0000000000..c4a841a992 --- /dev/null +++ b/tee-worker/scripts/changelog/templates/_free_notes.md.tera @@ -0,0 +1,10 @@ + +{# This file uses the Markdown format with additional templating such as this comment. -#} +{# Such a comment will not show up in the rendered release notes. -#} +{# The content of this file (if any) will be inserted at the top of the release notes -#} +{# and generated for each new release candidate. -#} +{# Ensure you leave an empty line at both top and bottom of this file. -#} + + + + diff --git a/tee-worker/scripts/changelog/templates/challenge_level.md.tera b/tee-worker/scripts/changelog/templates/challenge_level.md.tera new file mode 100644 index 0000000000..c4a8934fd4 --- /dev/null +++ b/tee-worker/scripts/changelog/templates/challenge_level.md.tera @@ -0,0 +1,37 @@ +{%- import "change.md.tera" as m_c -%} + +{# This macro convert a merge challenge level into readable output #} +{%- macro challenge_level(e, changes) -%} + +{%- if e >= 5 -%} + {%- set level = "‼️ Breaking Changes" -%} + {%- set text = "This release contains **breaking changes**. Be sure to upgrade the affected interfaces." -%} +{%- elif e >= 3 -%} + {%- set level = "❗️ Attention" -%} + {%- set text = "This release contains some non-trivial updates. Be mindful when upgrading." -%} +{%- else -%} + {%- set level = "Trivial" -%} + {%- set text = "This release contains relatively small updates." -%} +{%- endif %} + + + + +{%- if level %} +{{level}}: {{text}} + +{% if e >= 3 %} +The changes motivating this challenge level are: +{% for pr in changes | sort(attribute="merged_at") -%} + {%- if pr.meta.E -%} + {%- if pr.meta.E.value == e %} +- {{ m_c::change(c=pr) }} + {%- endif -%} + {%- endif -%} +{%- endfor -%} +{%- else -%} + +{%- endif -%} +{%- endif -%} + +{%- endmacro level -%} diff --git a/tee-worker/scripts/changelog/templates/change.md.tera b/tee-worker/scripts/changelog/templates/change.md.tera new file mode 100644 index 0000000000..25cc04edec --- /dev/null +++ b/tee-worker/scripts/changelog/templates/change.md.tera @@ -0,0 +1,42 @@ +{# This macro shows ONE change #} +{%- macro change(c, cml="[C]", pal="[P]", wor="[W]") -%} + +{%- if c.meta.C and c.meta.C.value >= 7 -%} +{%- set prio = " ‼️ HIGH" -%} +{%- elif c.meta.C and c.meta.C.value >= 3 -%} +{%- set prio = " ❗️ Medium" -%} +{%- elif c.meta.C and c.meta.C.value < 3 -%} +{%- set prio = " Low" -%} +{%- else -%} +{%- set prio = "" -%} +{%- endif -%} + + +{%- if c.html_url is containing("worker") -%} +{%- set repo = wor -%} +{%- elif c.html_url is containing("pallets") -%} +{%- set repo = pal -%} +{%- else -%} +{%- set repo = " " -%} +{%- endif -%} + +{# For now don't show pallets or worker #} +{%- set repo = " " -%} + +{%- if c.meta.E and c.meta.E.value >= 7 -%} +{%- set challenge = " 💥 breaking changes " -%} +{%- elif c.meta.E and c.meta.E.value == 6 -%} +{%- set challenge = " ⚡ breaks parentchain interface " -%} +{%- elif c.meta.E and c.meta.E.value == 5 -%} +{%- set challenge = " 🔥 breaks public rpc api " -%} +{%- elif c.meta.E and c.meta.E.value >= 3 -%} +{%- set challenge = " 📢 attention required " -%} +{%- elif c.meta.E and c.meta.E.value < 3 -%} +{%- set challenge = " ✅ easy merge " -%} +{%- else -%} +{%- set challenge = "" -%} +{%- endif -%} + + +{{- repo }} {{ challenge }}[`#{{c.number}}`]({{c.html_url}}) {{- prio }} - {{ c.title | capitalize | truncate(length=120, end="…") }} +{%- endmacro change -%} \ No newline at end of file diff --git a/tee-worker/scripts/changelog/templates/changes.md.tera b/tee-worker/scripts/changelog/templates/changes.md.tera new file mode 100644 index 0000000000..571f2f4cab --- /dev/null +++ b/tee-worker/scripts/changelog/templates/changes.md.tera @@ -0,0 +1,24 @@ +{# This include generates the section showing the changes #} +## Changes + +{# for not now printed until pallet is actually included #} +{# ### Legend #} + +{# - {{ WOR }} Worker #} +{# - {{ PAL }} Pallet #} + +{% include "changes_applibs.md.tera" %} + +{% include "changes_client.md.tera" %} + +{% include "changes_core.md.tera" %} + +{% include "changes_evm.md.tera" %} + +{% include "changes_offchain.md.tera" %} + +{% include "changes_sidechain.md.tera" %} + +{% include "changes_teeracle.md.tera" %} + +{% include "changes_misc.md.tera" %} diff --git a/tee-worker/scripts/changelog/templates/changes_applibs.md.tera b/tee-worker/scripts/changelog/templates/changes_applibs.md.tera new file mode 100644 index 0000000000..db393f764e --- /dev/null +++ b/tee-worker/scripts/changelog/templates/changes_applibs.md.tera @@ -0,0 +1,17 @@ +{% import "change.md.tera" as m_c -%} +### App-Libs + +{#- The changes are sorted by merge date #} +{%- for pr in changes | sort(attribute="merged_at") %} + +{%- if pr.meta.B %} + {%- if pr.meta.B.value == 0 %} + {#- We skip silent ones -#} + {%- else -%} + + {%- if pr.meta.A.value == 2 %} +- {{ m_c::change(c=pr) }} + {%- endif -%} + {% endif -%} + {% endif -%} +{% endfor %} diff --git a/tee-worker/scripts/changelog/templates/changes_client.md.tera b/tee-worker/scripts/changelog/templates/changes_client.md.tera new file mode 100644 index 0000000000..5e96861812 --- /dev/null +++ b/tee-worker/scripts/changelog/templates/changes_client.md.tera @@ -0,0 +1,17 @@ +{% import "change.md.tera" as m_c -%} +### Client + +{#- The changes are sorted by merge date #} +{%- for pr in changes | sort(attribute="merged_at") %} + +{%- if pr.meta.B %} + {%- if pr.meta.B.value == 0 %} + {#- We skip silent ones -#} + {%- else -%} + + {%- if pr.meta.A.value == 1 %} +- {{ m_c::change(c=pr) }} + {%- endif -%} + {% endif -%} + {% endif -%} +{% endfor %} diff --git a/tee-worker/scripts/changelog/templates/changes_core.md.tera b/tee-worker/scripts/changelog/templates/changes_core.md.tera new file mode 100644 index 0000000000..f88447b9e9 --- /dev/null +++ b/tee-worker/scripts/changelog/templates/changes_core.md.tera @@ -0,0 +1,17 @@ +{% import "change.md.tera" as m_c -%} +### Core + +{#- The changes are sorted by merge date #} +{%- for pr in changes | sort(attribute="merged_at") %} + +{%- if pr.meta.B %} + {%- if pr.meta.B.value == 0 %} + {#- We skip silent ones -#} + {%- else -%} + + {%- if pr.meta.A.value == 0 %} +- {{ m_c::change(c=pr) }} + {%- endif -%} + {% endif -%} + {% endif -%} +{% endfor %} diff --git a/tee-worker/scripts/changelog/templates/changes_evm.md.tera b/tee-worker/scripts/changelog/templates/changes_evm.md.tera new file mode 100644 index 0000000000..92747435fd --- /dev/null +++ b/tee-worker/scripts/changelog/templates/changes_evm.md.tera @@ -0,0 +1,17 @@ +{% import "change.md.tera" as m_c -%} +### EVM Feature + +{#- The changes are sorted by merge date #} +{%- for pr in changes | sort(attribute="merged_at") %} + +{%- if pr.meta.B %} + {%- if pr.meta.B.value == 0 %} + {#- We skip silent ones -#} + {%- else -%} + + {%- if pr.meta.A.value == 6 %} +- {{ m_c::change(c=pr) }} + {%- endif -%} + {% endif -%} + {% endif -%} +{% endfor %} diff --git a/tee-worker/scripts/changelog/templates/changes_misc.md.tera b/tee-worker/scripts/changelog/templates/changes_misc.md.tera new file mode 100644 index 0000000000..1beb2efd91 --- /dev/null +++ b/tee-worker/scripts/changelog/templates/changes_misc.md.tera @@ -0,0 +1,37 @@ +{%- import "change.md.tera" as m_c -%} + +{%- set_global misc_count = 0 -%} +{#- First pass to count #} +{%- for pr in changes -%} + {%- if pr.meta.B %} + {%- if pr.meta.B.value == 0 -%} + {#- We skip silent ones -#} + {%- else -%} +{%- set_global misc_count = misc_count + 1 -%} + {% endif -%} + {% endif -%} +{% endfor -%} + +### Misc + +{% if misc_count > 10 %} +There are other misc. changes. You can expand the list below to view them all. +
Other misc. changes +{% endif -%} + +{#- The changes are sorted by merge date #} +{%- for pr in changes | sort(attribute="merged_at") %} + {%- if pr.meta.B %} + {%- if pr.meta.B.value == 0 %} + {#- We skip silent ones -#} + {%- else -%} + {%- if pr.meta.B.value >= 1 %} +- {{ m_c::change(c=pr) }} + {%- endif -%} + {% endif -%} + {% endif -%} +{% endfor %} + +{% if misc_count > 10 %} +
+{% endif -%} diff --git a/tee-worker/scripts/changelog/templates/changes_offchain.md.tera b/tee-worker/scripts/changelog/templates/changes_offchain.md.tera new file mode 100644 index 0000000000..d298752043 --- /dev/null +++ b/tee-worker/scripts/changelog/templates/changes_offchain.md.tera @@ -0,0 +1,17 @@ +{% import "change.md.tera" as m_c -%} +### Offchain + +{#- The changes are sorted by merge date #} +{%- for pr in changes | sort(attribute="merged_at") %} + +{%- if pr.meta.B %} + {%- if pr.meta.B.value == 0 %} + {#- We skip silent ones -#} + {%- else -%} + + {%- if pr.meta.A.value == 4 %} +- {{ m_c::change(c=pr) }} + {%- endif -%} + {% endif -%} + {% endif -%} +{% endfor %} diff --git a/tee-worker/scripts/changelog/templates/changes_sidechain.md.tera b/tee-worker/scripts/changelog/templates/changes_sidechain.md.tera new file mode 100644 index 0000000000..f953cfbcdf --- /dev/null +++ b/tee-worker/scripts/changelog/templates/changes_sidechain.md.tera @@ -0,0 +1,17 @@ +{% import "change.md.tera" as m_c -%} +### Sidechain + +{#- The changes are sorted by merge date #} +{%- for pr in changes | sort(attribute="merged_at") %} + +{%- if pr.meta.B %} + {%- if pr.meta.B.value == 0 %} + {#- We skip silent ones -#} + {%- else -%} + + {%- if pr.meta.A.value == 3 %} +- {{ m_c::change(c=pr) }} + {%- endif -%} + {% endif -%} + {% endif -%} +{% endfor %} diff --git a/tee-worker/scripts/changelog/templates/changes_teeracle.md.tera b/tee-worker/scripts/changelog/templates/changes_teeracle.md.tera new file mode 100644 index 0000000000..6e94e88b2c --- /dev/null +++ b/tee-worker/scripts/changelog/templates/changes_teeracle.md.tera @@ -0,0 +1,17 @@ +{% import "change.md.tera" as m_c -%} +### Teeracle + +{#- The changes are sorted by merge date #} +{%- for pr in changes | sort(attribute="merged_at") %} + +{%- if pr.meta.B %} + {%- if pr.meta.B.value == 0 %} + {#- We skip silent ones -#} + {%- else -%} + + {%- if pr.meta.A.value == 5 %} +- {{ m_c::change(c=pr) }} + {%- endif -%} + {% endif -%} + {% endif -%} +{% endfor %} diff --git a/tee-worker/scripts/changelog/templates/debug.md.tera b/tee-worker/scripts/changelog/templates/debug.md.tera new file mode 100644 index 0000000000..41f3702d7c --- /dev/null +++ b/tee-worker/scripts/changelog/templates/debug.md.tera @@ -0,0 +1,8 @@ +{%- set to_ignore = changes | filter(attribute="meta.B.value", value=0) %} + + diff --git a/tee-worker/scripts/changelog/templates/global_challenge_level.md.tera b/tee-worker/scripts/changelog/templates/global_challenge_level.md.tera new file mode 100644 index 0000000000..d2108dce4d --- /dev/null +++ b/tee-worker/scripts/changelog/templates/global_challenge_level.md.tera @@ -0,0 +1,26 @@ +{% import "challenge_level.md.tera" as m_p -%} +## Upgrade Challenge Level + +{%- set worker_prio = 0 -%} +{%- set pallet_prio = 0 -%} + +{# We fetch the various levels #} +{%- if worker.meta.E -%} +{%- set worker_level = worker.meta.E.max -%} +{%- else -%} +{%- set worker_level = 0 -%} +{%- endif -%} +{%- if pallet.meta.E -%} +{%- set pallet_level = pallet.meta.E.max -%} +{%- else -%} +{%- set pallet_level = 0 -%} +{%- endif -%} + +{# We compute the global level #} +{%- set global_level = worker_level -%} +{%- if pallet_level > global_level -%} +{%- set global_level = pallet_level -%} +{%- endif -%} + +{#- We show the result #} +{{ m_p::challenge_level(e=global_level, changes=changes) }} diff --git a/tee-worker/scripts/changelog/templates/global_priority.md.tera b/tee-worker/scripts/changelog/templates/global_priority.md.tera new file mode 100644 index 0000000000..87a6d52aaf --- /dev/null +++ b/tee-worker/scripts/changelog/templates/global_priority.md.tera @@ -0,0 +1,27 @@ +{% import "high_priority.md.tera" as m_p -%} +## Upgrade Priority + +{%- set worker_prio = 0 -%} +{%- set pallet_prio = 0 -%} + +{# We fetch the various priorities #} +{%- if worker.meta.C -%} +{%- set worker_prio = worker.meta.C.max -%} +{%- else -%} +{%- set worker_prio = 0 -%} +{%- endif -%} +{%- if pallet.meta.C -%} +{%- set pallet_prio = pallet.meta.C.max -%} +{%- else -%} +{%- set pallet_prio = 0 -%} +{%- endif -%} + +{# We compute the global priority #} +{%- set global_prio = worker_prio -%} +{%- if pallet_prio > global_prio -%} +{%- set global_prio = pallet_prio -%} +{%- endif -%} + + +{#- We show the result #} +{{ m_p::high_priority(p=global_prio, changes=changes) }} diff --git a/tee-worker/scripts/changelog/templates/high_priority.md.tera b/tee-worker/scripts/changelog/templates/high_priority.md.tera new file mode 100644 index 0000000000..117d335efd --- /dev/null +++ b/tee-worker/scripts/changelog/templates/high_priority.md.tera @@ -0,0 +1,38 @@ +{%- import "change.md.tera" as m_c -%} + +{# This macro convert a priority level into readable output #} +{%- macro high_priority(p, changes) -%} + +{%- if p >= 7 -%} + {%- set prio = "‼️ HIGH" -%} + {%- set text = "This is a **high priority** release and you must upgrade as as soon as possible." -%} +{%- elif p >= 3 -%} + {%- set prio = "❗️ Medium" -%} + {%- set text = "This is a medium priority release and you should upgrade in a timely manner." -%} +{%- else -%} + {%- set prio = "Low" -%} + {%- set text = "This is a low priority release and you may upgrade at your convenience." -%} +{%- endif %} + + + +{%- if prio %} +{{prio}}: {{text}} + +{% if p >= 3 %} +The changes motivating this priority level are: +{% for pr in changes | sort(attribute="merged_at") -%} + {%- if pr.meta.C -%} + {%- if pr.meta.C.value == p %} +- {{ m_c::change(c=pr) }} +{%- if pr.meta.B and pr.meta.B.value == 7 %} (RUNTIME) +{% endif %} + {%- endif -%} + {%- endif -%} +{%- endfor -%} +{%- else -%} + +{%- endif -%} +{%- endif -%} + +{%- endmacro priority -%} diff --git a/tee-worker/scripts/changelog/templates/pre_release.md.tera b/tee-worker/scripts/changelog/templates/pre_release.md.tera new file mode 100644 index 0000000000..7d4ad42dd8 --- /dev/null +++ b/tee-worker/scripts/changelog/templates/pre_release.md.tera @@ -0,0 +1,11 @@ +{%- if env.PRE_RELEASE == "true" -%} +
⚠️ This is a pre-release + +**Release candidates** are **pre-releases** and may not be final. +Although they are reasonably tested, there may be additional changes or issues +before an official release is tagged. Use at your own discretion, and consider +only using final releases on critical production infrastructure. +
+{% else -%} + +{%- endif %} diff --git a/tee-worker/scripts/changelog/templates/template.md.tera b/tee-worker/scripts/changelog/templates/template.md.tera new file mode 100644 index 0000000000..2c61f3d5a1 --- /dev/null +++ b/tee-worker/scripts/changelog/templates/template.md.tera @@ -0,0 +1,33 @@ +{# This is the entry point of the template -#} + +{% include "pre_release.md.tera" -%} + +{% if env.PRE_RELEASE == "true" -%} +This pre-release contains the changes from `{{ env.REF1 | replace(from="refs/tags/", to="") }}` to `{{ env.REF2 | +replace(from="refs/tags/", to="") }}`. +{%- else -%} +This release contains the changes from `{{ env.REF1 | replace(from="refs/tags/", to="") }}` to `{{ env.REF2 | +replace(from="refs/tags/", to="") }}`. +{% endif -%} + +{# -- For now no pallet changes included -- #} +{# {%- set changes = worker.changes | concat(with=pallet.changes) -%}##} +{%- set changes = worker.changes -%} +{%- include "debug.md.tera" -%} + +{%- set CML = "[C]" -%} +{%- set WOR = "[W]" -%} +{%- set PAL = "[P]" -%} + +{# -- Manual free notes section -- #} +{% include "_free_notes.md.tera" -%} + +{# -- Important automatic section -- #} +{% include "global_priority.md.tera" -%} + +{# -- Important automatic section -- #} +{% include "global_challenge_level.md.tera" -%} + +{# --------------------------------- #} + +{% include "changes.md.tera" -%} diff --git a/tee-worker/scripts/https_test.sh b/tee-worker/scripts/https_test.sh new file mode 100755 index 0000000000..0e00523f4a --- /dev/null +++ b/tee-worker/scripts/https_test.sh @@ -0,0 +1,19 @@ +#!/bin/bash +set -euo pipefail + +# Runs https test demo: Either set `CLIENT_DIR` env var directly or run script with: +# +# source ./init_env.sh && ./https_test.sh + +echo "$CLIENT_DIR" + +cd "$CLIENT_DIR" || exit + +LOG_1="${LOG_1:-$LOG_DIR/https_test.log}" + +echo "[https_test.sh] printing to logs:" +echo " $LOG_1" + +touch "$LOG_1" + +./demo_https_test.sh -p 9944 -P 2000 -t first 2>&1 | tee "$LOG_1" diff --git a/tee-worker/scripts/init_env.sh b/tee-worker/scripts/init_env.sh new file mode 100755 index 0000000000..9b68a64b22 --- /dev/null +++ b/tee-worker/scripts/init_env.sh @@ -0,0 +1,15 @@ +#!/bin/bash + +# script that sets the correct environment variables to execute other scripts + +export SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" +export PROJ_ROOT="$(dirname "$SCRIPT_DIR")" +export CLIENT_DIR="$PROJ_ROOT/cli" +export LOG_DIR="$PROJ_ROOT/log" +export CI_DIR="$PROJ_ROOT/ci" +export RUST_LOG=info,ws=warn,substrate_api_client=warn,ac_node_api=warn + +echo "Set environment variables:" +echo " BASH_SCRIPT_DIR: $SCRIPT_DIR" +echo " PROJ_ROOT: $PROJ_ROOT" +echo " CLIENT_DIR: $CLIENT_DIR" \ No newline at end of file diff --git a/tee-worker/scripts/launch_local_worker.sh b/tee-worker/scripts/launch_local_worker.sh new file mode 100755 index 0000000000..50c0708086 --- /dev/null +++ b/tee-worker/scripts/launch_local_worker.sh @@ -0,0 +1,80 @@ +#!/usr/bin/env bash + +# Usage: ./launch_local_worker.sh workers(number) [true|false]" +# Example: ./launch_local_worker.sh 2 true + +ROOTDIR=$(git rev-parse --show-toplevel) +ROOTDIR="$ROOTDIR/tee-worker" + +workers=${1:-1} +reset=$2 +option_clean="" +option_dev="" +option_request_state="" +if [ "${reset}" = 'true' ]; then + option_clean="--clean-reset" + option_dev="--dev" +fi + +worker_endpoint="localhost" +#node_url="ws://integritee-node" +#node_port="9912" +node_url="ws://host.docker.internal" +node_port="9946" + +RUST_LOG="info,integritee_service=info,ws=warn,sp_io=error,substrate_api_client=warn,\ +itc_parentchain_light_client=debug,\ +jsonrpsee_ws_client=warn,jsonrpsee_ws_server=warn, enclave_runtime=warn,ita_stf=debug,\ +its_rpc_handler=warn,itc_rpc_client=warn,its_consensus_common=debug,its_state=warn,\ +its_consensus_aura=warn,aura*=warn,its_consensus_slots=warn" + +#./integritee-service init-shard H8wzxGBcKa1k5tXMALACo9P7uKS5rYFL8e3mMAEVe7Ln +echo "Number of workers: ${workers}" + + +for ((i = 0; i < workers; i++)); do + if ((i > 0)); then + option_request_state="--request-state" + fi + + worker_name="worker${i}" + if [ "${reset}" = 'true' ]; then + echo "clear dir: $ROOTDIR/tmp/${worker_name}" + rm -rf "$ROOTDIR"/tmp/"${worker_name}" + fi + mkdir -p "$ROOTDIR"/tmp/"${worker_name}" + for Item in 'enclave.signed.so' 'key.txt' 'spid.txt' 'integritee-service' 'integritee-cli'; do + cp "${ROOTDIR}/bin/${Item}" "$ROOTDIR"/tmp/"${worker_name}" + done + echo "" + echo "--------------------setup worker(${worker_name})----------------------------------------" + + cd "${ROOTDIR}"/tmp/${worker_name} || exit + echo "enter ${ROOTDIR}/tmp/${worker_name}" + + mu_ra_port=$((3443 + i)) + untrusted_http_port=$((4545 + i)) + trusted_worker_port=$((2000 + i)) + untrusted_worker_port=$((3000 + i)) + echo "${worker_name} ports: + mu-ra-port: ${mu_ra_port} + untrusted-http-port: ${untrusted_http_port} + trusted-worker-port: ${trusted_worker_port} + untrusted-worker-port: ${untrusted_worker_port} + " + + launch_command="RUST_LOG=${RUST_LOG} ./integritee-service ${option_clean} \ +--mu-ra-external-address ${worker_endpoint} --mu-ra-port ${mu_ra_port} --untrusted-http-port ${untrusted_http_port} --ws-external \ +--trusted-external-address wss://${worker_endpoint} --trusted-worker-port ${trusted_worker_port} \ +--untrusted-external-address ws://${worker_endpoint} --untrusted-worker-port ${untrusted_worker_port} \ +--node-url ${node_url} --node-port ${node_port} \ +run --skip-ra ${option_dev} ${option_request_state}" + + echo "${worker_name} command: ${launch_command}" + eval "${launch_command}" >"${ROOTDIR}"/log/${worker_name}.log 2>&1 & + echo "${worker_name}(integritee-service) started successfully. log: ${ROOTDIR}/log/${worker_name}.log" + + if ((workers > 0)); then + "${ROOTDIR}"/dockerize -wait-retry-interval 10s -wait http://localhost:${untrusted_http_port}/is_initialized -timeout 600s + fi +done diff --git a/tee-worker/scripts/litentry/build_parachain_docker.sh b/tee-worker/scripts/litentry/build_parachain_docker.sh new file mode 100755 index 0000000000..31e2c2209c --- /dev/null +++ b/tee-worker/scripts/litentry/build_parachain_docker.sh @@ -0,0 +1,18 @@ +#!/bin/bash +set -euo pipefail + +ROOTDIR=$(git rev-parse --show-toplevel) +ROOTDIR="$ROOTDIR/tee-worker" + +cd "$ROOTDIR" +# with '#' so that it filters out the pallet dependency +SHA=$(grep -F 'https://github.com/litentry/litentry-parachain.git?branch=tee-dev#' Cargo.lock | head -n1 | sed 's/.*#//;s/"$//') + +PARACHAIN_DIR=/tmp/litentry-parachain +[ -d "$PARACHAIN_DIR" ] && rm -rf "$PARACHAIN_DIR" +git clone https://github.com/litentry/litentry-parachain "$PARACHAIN_DIR" +cd "$PARACHAIN_DIR" +git checkout tee-dev +git checkout "$SHA" + +./scripts/build-docker.sh release tee-dev --features=tee-dev \ No newline at end of file diff --git a/tee-worker/scripts/litentry/cleanup.sh b/tee-worker/scripts/litentry/cleanup.sh new file mode 100755 index 0000000000..66146d0773 --- /dev/null +++ b/tee-worker/scripts/litentry/cleanup.sh @@ -0,0 +1,12 @@ +#!/bin/bash + +set -eo pipefail + +pid=$(ps aux | grep '[l]ocal-setup/launch' | awk '{print $2}') + +if [ ! -z "$pid" ]; then + echo "killing $pid" + kill -9 "$pid" +fi + +killall integritee-service 2>/dev/null || true diff --git a/tee-worker/scripts/litentry/generate_parachain_artefacts.sh b/tee-worker/scripts/litentry/generate_parachain_artefacts.sh new file mode 100755 index 0000000000..63b0dcec73 --- /dev/null +++ b/tee-worker/scripts/litentry/generate_parachain_artefacts.sh @@ -0,0 +1,19 @@ +#!/bin/bash +set -euo pipefail + +ROOTDIR=$(git rev-parse --show-toplevel) +ROOTDIR="$ROOTDIR/tee-worker" +DESTDIR="$ROOTDIR/docker/litentry" +PARACHAIN_DIR=$(mktemp -d) +git clone https://github.com/litentry/litentry-parachain "$PARACHAIN_DIR" +cd "$PARACHAIN_DIR" +git checkout tee-dev + +cp -f docker/rococo-parachain-launch-config.tee-dev.yml docker/rococo-parachain-launch-config.yml +# generate files +make generate-docker-compose-rococo +# copy files over to `DESTDIR` +mkdir -p "$DESTDIR" +cp docker/generated-rococo/* "$DESTDIR/" +# clean up +rm -rf "$PARACHAIN_DIR" \ No newline at end of file diff --git a/tee-worker/scripts/litentry/generate_upstream_patch.sh b/tee-worker/scripts/litentry/generate_upstream_patch.sh new file mode 100755 index 0000000000..25e16a2e99 --- /dev/null +++ b/tee-worker/scripts/litentry/generate_upstream_patch.sh @@ -0,0 +1,59 @@ +#!/bin/bash + +set -eo pipefail + +cleanup() { + rm -rf "$1" + echo "cleaned up $1" +} + +# This script generates a patch for the diffs between commit-A and commit-B +# of the upstream repo (https://github.com/integritee-network/worker), where +# commit-A: the commit recorded in tee-worker/upstream_commit +# commit-B: the HEAD commit of upstream master or a given commit +# +# The patch will be generated under tee-worker/upstream.patch +# to apply this patch: +# git am -3 --exclude=Cargo.lock --exclude=enclave-runtime/Cargo.lock < upstream.patch + +UPSTREAM="https://github.com/integritee-network/worker" +ROOTDIR=$(git rev-parse --show-toplevel) +ROOTDIR="$ROOTDIR/tee-worker" +cd "$ROOTDIR" + +if [ -f upstream_commit ]; then + OLD_COMMIT=$(head -1 upstream_commit) +else + echo "Can't find upstream_commit file, quit" + exit 1 +fi + +if [ "$(git remote get-url upstream 2>/dev/null)" != "$UPSTREAM" ]; then + echo "please set your upstream origin to $UPSTREAM" + exit 1 +else + git fetch -q upstream +fi + +TMPDIR=$(mktemp -d) +trap 'cleanup "$TMPDIR"' ERR EXIT INT + +cd "$TMPDIR" +echo "cloning $UPSTREAM ..." +git clone -q "$UPSTREAM" worker +cd worker +[ ! -z "$1" ] && git checkout "$1" +echo "generating patch ..." +git diff $OLD_COMMIT HEAD > "$ROOTDIR/upstream.patch" +git rev-parse --short HEAD > "$ROOTDIR/upstream_commit" + +echo "=======================================================================" +echo "upstream_commit is updated." +echo "be sure to fetch the upstream to update the hashes of files." +echo "upstream.patch is generated, to apply it, run:" +echo ' git am -3 --exclude=Cargo.lock --exclude=enclave-runtime/Cargo.lock < upstream.patch' +echo "after that, please:" +echo "- resolve any conflicts" +echo "- optionally update both Cargo.lock files" +echo "- apply the changes to /.github/workflows/tee-worker-ci.yml" +echo "=======================================================================" diff --git a/tee-worker/scripts/litentry/identity_test.sh b/tee-worker/scripts/litentry/identity_test.sh new file mode 100755 index 0000000000..7287aff8cc --- /dev/null +++ b/tee-worker/scripts/litentry/identity_test.sh @@ -0,0 +1,37 @@ +#!/usr/bin/env bash + +root_dir=$(git rev-parse --show-toplevel) +root_dir="$root_dir/tee-worker" + +#CLIENT_BIN="$root_dir/bin/integritee-cli" + +#NODE PORT +node_port=9912 +node_url=ws://integritee-node + +worker_url=wss://tee-builder +worker_port=2000 + +CLIENT="./integritee-cli --node-url ${node_url} --node-port ${node_port} --worker-url ${worker_url} --trusted-worker-port ${worker_port}" +#cd ${root_dir}/tmp/worker1 + +cd ${root_dir}/bin +./integritee-service mrenclave | tee ~/mrenclave.b58 +MRENCLAVE=$(cat ~/mrenclave.b58) + +cd ${root_dir}/tmp/worker1 + +# node-js: tweet_id: Buffer.from("1571829863862116352").toJSON().data.toString() +validation_data='{"Web2":{"Twitter":{"tweet_id":[49,53,55,49,56,50,57,56,54,51,56,54,50,49,49,54,51,53,50]}}}' + +# node-js: twitter_username: Buffer.from("litentry").toJSON().data.toString() +identity='{"web_type":{"Web2":"Twitter"},"handle":{"String":[108,105,116,101,110,116,114,121]}}' + +echo "link_identity" +RUST_LOG=warn ${CLIENT} trusted --mrenclave ${MRENCLAVE} link-identity "//Alice" "$identity" + +echo "set-challenge-code" +${CLIENT} trusted --mrenclave ${MRENCLAVE} set-challenge-code "//Alice" "$identity" 1134 + +echo "verify-identity-preflight" +RUST_LOG=info ${CLIENT} trusted --mrenclave ${MRENCLAVE} verify-identity-preflight "//Alice" "$identity" "$validation_data" diff --git a/tee-worker/scripts/litentry/start_parachain.sh b/tee-worker/scripts/litentry/start_parachain.sh new file mode 100755 index 0000000000..16da54db8e --- /dev/null +++ b/tee-worker/scripts/litentry/start_parachain.sh @@ -0,0 +1,12 @@ +#!/bin/bash +set -euo pipefail + +PARACHAIN_DIR=/tmp/litentry-parachain +[ -d "$PARACHAIN_DIR" ] && rm -rf "$PARACHAIN_DIR" +git clone https://github.com/litentry/litentry-parachain "$PARACHAIN_DIR" +cd "$PARACHAIN_DIR" +git checkout tee-dev + +cp -f docker/rococo-parachain-launch-config.tee-dev.yml docker/rococo-parachain-launch-config.yml + +make launch-docker-rococo \ No newline at end of file diff --git a/tee-worker/scripts/litentry/stop_parachain.sh b/tee-worker/scripts/litentry/stop_parachain.sh new file mode 100755 index 0000000000..8f0058118d --- /dev/null +++ b/tee-worker/scripts/litentry/stop_parachain.sh @@ -0,0 +1,8 @@ +#!/bin/bash +set -euo pipefail + +PARACHAIN_DIR=/tmp/litentry-parachain + +cd "$PARACHAIN_DIR" +make clean-docker-rococo || true +rm -rf "$PARACHAIN_DIR" \ No newline at end of file diff --git a/tee-worker/scripts/litentry/ubuntu_setup.sh b/tee-worker/scripts/litentry/ubuntu_setup.sh new file mode 100755 index 0000000000..a41c36e962 --- /dev/null +++ b/tee-worker/scripts/litentry/ubuntu_setup.sh @@ -0,0 +1,70 @@ +#!/bin/bash + +set -eo pipefail + +# most is copied from +# https://github.com/apache/incubator-teaclave-sgx-sdk/blob/v1.1.4/dockerfile/Dockerfile.2004.nightly + +# install rust +curl -s https://sh.rustup.rs -sSf | sh -s -- -y +source ${HOME}/.cargo/env +rustup show + +# install substrate build deps +sudo apt-get update +sudo apt-get install -y cmake pkg-config libssl-dev git clang libclang-dev gnupg2 + +# install llvm +sudo apt-get update +wget https://apt.llvm.org/llvm.sh && chmod +x llvm.sh && sudo ./llvm.sh 10 + +# override binutils +wget https://download.01.org/intel-sgx/sgx-linux/2.15.1/as.ld.objdump.r4.tar.gz +tar xzf as.ld.objdump.r4.tar.gz +sudo cp -f external/toolset/ubuntu20.04/* /usr/bin/ + +# install sgx_sdk +SDK_URL="https://download.01.org/intel-sgx/sgx-linux/2.15.1/distro/ubuntu20.04-server/sgx_linux_x64_sdk_2.15.101.1.bin" +curl -o sdk.sh $SDK_URL +chmod a+x sdk.sh +echo -e 'no\n/opt' | ./sdk.sh +source /opt/sgxsdk/environment + +# install runtime sgx libs (psw) +CODENAME=focal +VERSION=2.15.101.1-focal1 +DCAP_VERSION=1.12.101.1-focal1 + +curl -fsSL https://download.01.org/intel-sgx/sgx_repo/ubuntu/intel-sgx-deb.key | sudo apt-key add - && \ +sudo add-apt-repository "deb https://download.01.org/intel-sgx/sgx_repo/ubuntu $CODENAME main" && \ +sudo apt-get update && \ +sudo apt-get install -y \ + libsgx-headers=$VERSION \ + libsgx-ae-epid=$VERSION \ + libsgx-ae-le=$VERSION \ + libsgx-ae-pce=$VERSION \ + libsgx-aesm-ecdsa-plugin=$VERSION \ + libsgx-aesm-epid-plugin=$VERSION \ + libsgx-aesm-launch-plugin=$VERSION \ + libsgx-aesm-pce-plugin=$VERSION \ + libsgx-aesm-quote-ex-plugin=$VERSION \ + libsgx-enclave-common=$VERSION \ + libsgx-enclave-common-dev=$VERSION \ + libsgx-epid=$VERSION \ + libsgx-epid-dev=$VERSION \ + libsgx-launch=$VERSION \ + libsgx-launch-dev=$VERSION \ + libsgx-quote-ex=$VERSION \ + libsgx-quote-ex-dev=$VERSION \ + libsgx-uae-service=$VERSION \ + libsgx-urts=$VERSION \ + sgx-aesm-service=$VERSION \ + libsgx-ae-qe3=$DCAP_VERSION \ + libsgx-pce-logic=$DCAP_VERSION \ + libsgx-qe3-logic=$DCAP_VERSION \ + libsgx-ra-network=$DCAP_VERSION \ + libsgx-ra-uefi=$DCAP_VERSION +mkdir -p /var/run/aesmd || true + +# store env +echo "$(env)" >> $GITHUB_ENV \ No newline at end of file diff --git a/tee-worker/scripts/m6.sh b/tee-worker/scripts/m6.sh new file mode 100755 index 0000000000..ba358db439 --- /dev/null +++ b/tee-worker/scripts/m6.sh @@ -0,0 +1,23 @@ +#!/bin/bash +set -euo pipefail + +# Runs M6 demo: Either set `CLIENT_DIR` env var directly or run script with: +# +# source ./init_env.sh && ./m6.sh + +echo "$CLIENT_DIR" + +cd "$CLIENT_DIR" || exit + +LOG_1="${LOG_1:-$LOG_DIR/m6_demo_shielding_unshielding_1.log}" +LOG_2="${LOG_2:-$LOG_DIR/m6_demo_shielding_unshielding_2.log}" + +echo "[m6.sh] printing to logs:" +echo " $LOG_1" +echo " $LOG_2" + +touch "$LOG_1" +touch "$LOG_2" + +./demo_shielding_unshielding.sh -p 9944 -P 2000 -C ./../bin/integritee-cli -t first 2>&1 | tee "$LOG_1" +./demo_shielding_unshielding.sh -p 9944 -P 3000 -C ./../bin/integritee-cli -t second 2>&1 | tee "$LOG_2" diff --git a/tee-worker/scripts/m8.sh b/tee-worker/scripts/m8.sh new file mode 100755 index 0000000000..95c92c26c4 --- /dev/null +++ b/tee-worker/scripts/m8.sh @@ -0,0 +1,21 @@ +#!/bin/bash +set -euo pipefail + +# Runs M8 demo: Either set `CLIENT_DIR` env var directly or run script with: +# +# source ./init_env.sh && ./m8.sh + +cd "$CLIENT_DIR" || exit + +LOG_1="${LOG_1:-$LOG_DIR/m8_demo_direct_call_1.log}" +LOG_2="${LOG_2:-$LOG_DIR/m8_demo_direct_call_2.log}" + +echo "[m8.sh] printing to logs:" +echo " $LOG_1" +echo " $LOG_2" + +touch "$LOG_1" +touch "$LOG_2" + +./demo_direct_call.sh -p 9944 -P 2000 -C ./../bin/integritee-cli -t first 2>&1 | tee "$LOG_1" +./demo_direct_call.sh -p 9944 -P 3000 -C ./../bin/integritee-cli -t second 2>&1 | tee "$LOG_2" diff --git a/tee-worker/scripts/polkadot_update.sh b/tee-worker/scripts/polkadot_update.sh new file mode 100755 index 0000000000..0ba52f86e3 --- /dev/null +++ b/tee-worker/scripts/polkadot_update.sh @@ -0,0 +1,97 @@ +#!/bin/bash + +# A script to automate the polkadot update for our repository as far as possible +# Needs the diener and sd (sed replacement) tool. Install with: +# cargo install diener +# cargo install sd + +# These are the values that need to be adjusted for an update +CHECKOUT_DIR="$HOME/polkadot_update2" +DEVELOPER_ID="tn" +OLD_VERSION_NUMBER="0.9.27" +NEW_VERSION_NUMBER="0.9.28" +NEW_NIGHTLY_VERSION="2022-09-12" + +OLD_POLKADOT_VERSION_NUMBER="polkadot-v${OLD_VERSION_NUMBER}" +NEW_POLKADOT_VERSION_NUMBER="polkadot-v${NEW_VERSION_NUMBER}" +DEVELOPMENT_BRANCH="${DEVELOPER_ID}/${NEW_POLKADOT_VERSION_NUMBER}" + +# Make sure that the directory does not exist. We don't want to mess up existing stuff +if [ -d "${CHECKOUT_DIR}" ]; then + echo "Directory ${CHECKOUT_DIR} already exists. Please delete directory first." + exit 1 +fi + +mkdir "${CHECKOUT_DIR}" +pushd "${CHECKOUT_DIR}" + +git clone https://github.com/integritee-network/integritee-node.git +git clone https://github.com/integritee-network/pallets.git +git clone https://github.com/integritee-network/parachain.git +git clone https://github.com/scs/substrate-api-client.git +git clone https://github.com/integritee-network/worker.git + +declare -a REPO_NAMES=("integritee-node" "pallets" "parachain" "substrate-api-client" "worker" ) + +# Create new branch for all repos +for REPO in ${REPO_NAMES[@]}; do + pushd ${REPO};git checkout -b ${DEVELOPMENT_BRANCH};popd +done + +# Update the polkadot version +# We cannot combine the flags into a single call. Don't use the all flag because it relly changes all dependencies +diener update --cumulus --branch ${NEW_POLKADOT_VERSION_NUMBER} +diener update --substrate --branch ${NEW_POLKADOT_VERSION_NUMBER} +# Polkadot uses another branch pattern, because why not... +diener update --polkadot --branch "release-v${NEW_VERSION_NUMBER}" + +# Add commit for all repos +for REPO in ${REPO_NAMES[@]}; do + pushd ${REPO};git add -A;git commit -m "Update polkadot version (Auto generated commit)";popd +done + +# Execute cargo update for all repos. Currently not active as it is not clear when is the "right moment" to do this +#for REPO in ${REPO_NAMES[@]}; do +# pushd ${REPO};cargo update;popd +#done + +# Add commit for all repos +#for REPO in ${REPO_NAMES[@]}; do +# pushd ${REPO};git add -A;git commit -m "Run cargo update (Auto generated)";popd +#done + +#set -o xtrace +# Update internal dependencies by doing search replace +for REPO in ${REPO_NAMES[@]}; do + SEARCH_STRING_VERSION="${REPO}\", branch = \"${OLD_POLKADOT_VERSION_NUMBER}\"" + SEARCH_STRING_VERSION_GIT="${REPO}.git\", branch = \"${OLD_POLKADOT_VERSION_NUMBER}\"" + SEARCH_STRING_MASTER="${REPO}\", branch = \"master\"" + SEARCH_STRING_MASTER_GIT="${REPO}.git\", branch = \"master\"" + REPLACE_STRING="${REPO}.git\", branch = \"${DEVELOPMENT_BRANCH}\"" + sd "${SEARCH_STRING_VERSION}" "${REPLACE_STRING}" $(find . -type f -name 'Cargo.toml') + sd "${SEARCH_STRING_VERSION_GIT}" "${REPLACE_STRING}" $(find . -type f -name 'Cargo.toml') + sd "${SEARCH_STRING_MASTER}" "${REPLACE_STRING}" $(find . -type f -name 'Cargo.toml') + sd "${SEARCH_STRING_MASTER_GIT}" "${REPLACE_STRING}" $(find . -type f -name 'Cargo.toml') +done + +# Add commit for all repos +for REPO in ${REPO_NAMES[@]}; do + pushd ${REPO};git add -A;git commit -m "Update versions for internal dependencies (Auto generated commit)";popd +done + +NIGHTLY_SEARCH_STRING="channel = \"nightly-.*\"" +NIGHTLY_SEARCH_STRING="channel = \"nightly-${NEW_NIGHTLY_VERSION}\"" +sd "${NIGHTLY_SEARCH_STRING}" "${NIGTHLY_NEW_STRING}" $(find . -type f -name 'rust-toolchain.toml') + +# Add commit for all repos +for REPO in ${REPO_NAMES[@]}; do + pushd ${REPO};git add -A;git commit -m "Update rust toolchain to new nightly version (Auto generated commit)";popd +done + +echo "" +echo "" +echo "Search results for old version number ${OLD_VERSION_NUMBER} in Cargo.toml files:" +# Exclude the lock files as they still refer to the old version +grep -F -r --exclude *.lock "${OLD_VERSION_NUMBER}" . + +popd diff --git a/tee-worker/scripts/sidechain.sh b/tee-worker/scripts/sidechain.sh new file mode 100755 index 0000000000..0ace651c38 --- /dev/null +++ b/tee-worker/scripts/sidechain.sh @@ -0,0 +1,17 @@ +#!/bin/bash +set -euo pipefail + +# Runs sidechain demo: Either set `CLIENT_DIR` env var directly or run script with: +# +# source ./init_env.sh && ./sidechain.sh + +cd "$CLIENT_DIR" || exit + +LOG="${LOG:-$LOG_DIR/sidechain_demo.log}" + +echo "[sidechain.sh] printing to logs:" +echo " $LOG" + +touch "$LOG" + +./demo_sidechain.sh -p 9944 -A 2000 -B 3000 -C ./../bin/integritee-cli 2>&1 | tee "$LOG" \ No newline at end of file diff --git a/tee-worker/scripts/teeracle.sh b/tee-worker/scripts/teeracle.sh new file mode 100644 index 0000000000..829c67b2a3 --- /dev/null +++ b/tee-worker/scripts/teeracle.sh @@ -0,0 +1,19 @@ +#!/bin/bash +set -euo pipefail + +# Runs Teeracle1 demo: Either set `CLIENT_DIR` env var directly or run script with: +# +# source ./init_env.sh && ./teeracle.sh + +echo "$CLIENT_DIR" + +cd "$CLIENT_DIR" || exit + +LOG_1="${LOG_1:-$LOG_DIR/teeracle1_demo_whitelist.log}" + +echo "[teeracle.sh] printing to logs:" +echo " $LOG_1" + +touch "$LOG_1" + +./demo_teeracle_whitelist.sh -p 9944 -P 2000 -d 120 -i 24 2>&1 | tee "$LOG_1" diff --git a/tee-worker/service/Cargo.toml b/tee-worker/service/Cargo.toml new file mode 100644 index 0000000000..033b8f7a59 --- /dev/null +++ b/tee-worker/service/Cargo.toml @@ -0,0 +1,90 @@ +[package] +authors = ["Integritee AG "] +build = "build.rs" +edition = "2021" +name = "integritee-service" +version = "0.9.0" + +[dependencies] +async-trait = "0.1.50" +base58 = "0.2" +clap = { version = "2.33", features = ["yaml"] } +dirs = "3.0.2" +env_logger = "0.9" +futures = "0.3" +hex = "0.4.3" +jsonrpsee = { version = "0.2.0", features = ["client", "ws-server", "macros"] } +lazy_static = "1.4.0" +log = "0.4" +parking_lot = "0.12.1" +parse_duration = "2.1.1" +# for litentry-parachain: otherwise we have a conflict in substrate-prometheus-endpoint +prometheus = { version = "0.13.0", features = ["process"], default-features = false } +serde = "1.0" +serde_derive = "1.0" +serde_json = "1.0" +thiserror = "1.0" +tokio = { version = "1.6.1", features = ["full"] } +warp = "0.3" + +# ipfs +ipfs-api = "0.11.0" +sha2 = { version = "0.7", default-features = false } + +codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } +primitive-types = { version = "0.11.1", default-features = false, features = ["codec"] } + +sgx_crypto_helper = { branch = "master", git = "https://github.com/apache/teaclave-sgx-sdk.git" } +sgx_types = { branch = "master", git = "https://github.com/apache/teaclave-sgx-sdk.git" } +sgx_urts = { branch = "master", git = "https://github.com/apache/teaclave-sgx-sdk.git" } + +# local +itc-parentchain = { path = "../core/parentchain/parentchain-crate" } +itc-rpc-client = { path = "../core/rpc-client" } +itc-rpc-server = { path = "../core/rpc-server" } +itp-enclave-api = { path = "../core-primitives/enclave-api" } +itp-enclave-metrics = { path = "../core-primitives/enclave-metrics" } +itp-node-api = { path = "../core-primitives/node-api" } +itp-settings = { path = "../core-primitives/settings" } +itp-types = { path = "../core-primitives/types" } +itp-utils = { path = "../core-primitives/utils" } +its-consensus-slots = { path = "../sidechain/consensus/slots" } +its-peer-fetch = { path = "../sidechain/peer-fetch" } +its-primitives = { path = "../sidechain/primitives" } +its-rpc-handler = { path = "../sidechain/rpc-handler" } +its-storage = { path = "../sidechain/storage" } + +# scs / integritee +my-node-runtime = { package = "rococo-parachain-runtime", git = "https://github.com/litentry/litentry-parachain.git", branch = "tee-dev" } +substrate-api-client = { git = "https://github.com/scs/substrate-api-client.git", branch = "polkadot-v0.9.29" } +teerex-primitives = { git = "https://github.com/integritee-network/pallets.git", branch = "master" } + +# Substrate dependencies +frame-support = { git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.29" } +frame-system = { git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.29" } +pallet-balances = { git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.29" } +sp-core = { git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.29" } +sp-finality-grandpa = { git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.29" } +sp-keyring = { git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.29" } +sp-runtime = { git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.29" } + +# mock server +lc-mock-server = { path = "../litentry/core/mock-server" } + +[features] +default = [] +evm = [] +mockserver = ["lc-mock-server/mockserver"] +offchain-worker = ["itp-settings/offchain-worker"] +production = ["itp-settings/production"] +sidechain = ["itp-settings/sidechain"] +teeracle = ["itp-settings/teeracle"] + +[dev-dependencies] +# crates.io +anyhow = "1.0.40" +mockall = "0.11" +# local +itc-parentchain-test = { path = "../core/parentchain/test" } +its-peer-fetch = { path = "../sidechain/peer-fetch", features = ["mocks"] } +its-test = { path = "../sidechain/test" } diff --git a/tee-worker/service/build.rs b/tee-worker/service/build.rs new file mode 100644 index 0000000000..71bde696fe --- /dev/null +++ b/tee-worker/service/build.rs @@ -0,0 +1,52 @@ +// Copyright (C) 2017-2018 Baidu, Inc. All Rights Reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in +// the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Baidu, Inc., nor the names of its +// contributors may be used to endorse or promote products derived +// from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +use std::env; + +fn main() { + let sdk_dir = env::var("SGX_SDK").unwrap_or_else(|_| "/opt/intel/sgxsdk".to_string()); + let is_sim = env::var("SGX_MODE").unwrap_or_else(|_| "HW".to_string()); + + // NOTE: if the crate is a workspace member rustc-paths are relative from the root directory + println!("cargo:rustc-link-search=native=./lib"); + println!("cargo:rustc-link-lib=static=Enclave_u"); + + println!("cargo:rustc-link-search=native={}/lib64", sdk_dir); + println!("cargo:rustc-link-lib=static=sgx_uprotected_fs"); + match is_sim.as_ref() { + "SW" => { + println!("cargo:rustc-link-lib=dylib=sgx_urts_sim"); + println!("cargo:rustc-link-lib=dylib=sgx_uae_service_sim"); + }, + _ => { + // HW by default + println!("cargo:rustc-link-lib=dylib=sgx_urts"); + println!("cargo:rustc-link-lib=dylib=sgx_uae_service"); + }, + } +} diff --git a/tee-worker/service/src/account_funding.rs b/tee-worker/service/src/account_funding.rs new file mode 100644 index 0000000000..787e74cbf1 --- /dev/null +++ b/tee-worker/service/src/account_funding.rs @@ -0,0 +1,162 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +use crate::error::{Error, ServiceResult}; +use codec::Encode; +use itp_node_api::api_client::{AccountApi, ParentchainApi}; +use itp_settings::worker::{ + EXISTENTIAL_DEPOSIT_FACTOR_FOR_INIT_FUNDS, REGISTERING_FEE_FACTOR_FOR_INIT_FUNDS, +}; +use log::*; +use sp_core::{ + crypto::{AccountId32, Ss58Codec}, + Pair, +}; +use sp_keyring::AccountKeyring; +use substrate_api_client::{Balance, GenericAddress, XtStatus}; + +/// Information about the enclave on-chain account. +pub trait EnclaveAccountInfo { + fn free_balance(&self) -> ServiceResult; +} + +pub struct EnclaveAccountInfoProvider { + node_api: ParentchainApi, + account_id: AccountId32, +} + +impl EnclaveAccountInfo for EnclaveAccountInfoProvider { + fn free_balance(&self) -> ServiceResult { + self.node_api.get_free_balance(&self.account_id).map_err(|e| e.into()) + } +} + +impl EnclaveAccountInfoProvider { + pub fn new(node_api: ParentchainApi, account_id: AccountId32) -> Self { + EnclaveAccountInfoProvider { node_api, account_id } + } +} + +pub fn setup_account_funding( + api: &ParentchainApi, + accountid: &AccountId32, + extrinsic_prefix: String, + is_development_mode: bool, +) -> ServiceResult<()> { + // Account funds + if is_development_mode { + // Development mode, the faucet will ensure that the enclave has enough funds + ensure_account_has_funds(api, accountid)?; + } else { + // Production mode, there is no faucet. + let registration_fees = enclave_registration_fees(api, &extrinsic_prefix)?; + info!("Registration fees = {:?}", registration_fees); + let free_balance = api.get_free_balance(accountid)?; + info!("TEE's free balance = {:?}", free_balance); + + let min_required_funds = + registration_fees.saturating_mul(REGISTERING_FEE_FACTOR_FOR_INIT_FUNDS); + let missing_funds = min_required_funds.saturating_sub(free_balance); + + if missing_funds > 0 { + // If there are not enough funds, then the user can send the missing TEER to the enclave address and start again. + println!( + "Enclave account: {:}, missing funds {}", + accountid.to_ss58check(), + missing_funds + ); + return Err(Error::Custom( + "Enclave does not have enough funds on the parentchain to register.".into(), + )) + } + } + Ok(()) +} + +// Alice plays the faucet and sends some funds to the account if balance is low +fn ensure_account_has_funds(api: &ParentchainApi, accountid: &AccountId32) -> Result<(), Error> { + // check account balance + let free_balance = api.get_free_balance(accountid)?; + info!("TEE's free balance = {:?} (Account: {})", free_balance, accountid); + + let existential_deposit = api.get_existential_deposit()?; + info!("Existential deposit is = {:?}", existential_deposit); + + let min_required_funds = + existential_deposit.saturating_mul(EXISTENTIAL_DEPOSIT_FACTOR_FOR_INIT_FUNDS); + let missing_funds = min_required_funds.saturating_sub(free_balance); + + if missing_funds > 0 { + info!("Transfer {:?} from Alice to {}", missing_funds, accountid); + bootstrap_funds_from_alice(api, accountid, missing_funds)?; + } + Ok(()) +} + +fn enclave_registration_fees(api: &ParentchainApi, xthex_prefixed: &str) -> Result { + let reg_fee_details = api.get_fee_details(xthex_prefixed, None)?; + match reg_fee_details { + Some(details) => match details.inclusion_fee { + Some(fee) => Ok(fee.inclusion_fee()), + None => Err(Error::Custom( + "Inclusion fee for the registration of the enclave is None!".into(), + )), + }, + None => + Err(Error::Custom("Fee Details for the registration of the enclave is None !".into())), + } +} + +// Alice sends some funds to the account +fn bootstrap_funds_from_alice( + api: &ParentchainApi, + accountid: &AccountId32, + funding_amount: u128, +) -> Result<(), Error> { + let alice = AccountKeyring::Alice.pair(); + info!("encoding Alice's public = {:?}", alice.public().0.encode()); + let alice_acc = AccountId32::from(*alice.public().as_array_ref()); + info!("encoding Alice's AccountId = {:?}", alice_acc.encode()); + + let alice_free = api.get_free_balance(&alice_acc)?; + info!(" Alice's free balance = {:?}", alice_free); + let nonce = api.get_nonce_of(&alice_acc)?; + info!(" Alice's Account Nonce is {}", nonce); + + if funding_amount > alice_free { + println!( + "funding amount is too high: please change EXISTENTIAL_DEPOSIT_FACTOR_FOR_INIT_FUNDS ({:?})", + funding_amount + ); + return Err(Error::ApplicationSetup) + } + + let mut alice_signer_api = api.clone(); + alice_signer_api.signer = Some(alice); + + println!("[+] bootstrap funding Enclave from Alice's funds"); + let xt = + alice_signer_api.balance_transfer(GenericAddress::Id(accountid.clone()), funding_amount); + let xt_hash = alice_signer_api.send_extrinsic(xt.hex_encode(), XtStatus::InBlock)?; + info!("[<] Extrinsic got included in a block. Hash: {:?}\n", xt_hash); + + // Verify funds have arrived. + let free_balance = alice_signer_api.get_free_balance(accountid); + info!("TEE's NEW free balance = {:?}", free_balance); + + Ok(()) +} diff --git a/tee-worker/service/src/cli.yml b/tee-worker/service/src/cli.yml new file mode 100644 index 0000000000..0af54bb323 --- /dev/null +++ b/tee-worker/service/src/cli.yml @@ -0,0 +1,172 @@ +name: "integritee-service" +version: "0.8.0" +about: Worker using Intel SGX TEE for Integritee-node +authors: "Integritee AG " + +# AppSettings can be defined as a list and are **not** ascii case sensitive +settings: + - ColoredHelp + - SubcommandRequired + +# All subcommands must be listed in the 'subcommand:' object, where the key to +# the list is the name of the subcommand, and all settings for that command are +# part of a Hash +args: + - node-server: + short: u + long: node-url + help: Set the node server protocol and IP address + takes_value: true + default_value: "ws://127.0.0.1" + - node-port: + short: p + long: node-port + help: Set the websocket port to listen for substrate events + takes_value: true + default_value: "9944" + - ws-external: + long: ws-external + help: Set this flag in case the worker should listen to external requests. + - mu-ra-port: + short: r + long: mu-ra-port + help: Set the websocket port to listen for mu-ra requests + takes_value: true + default_value: "3443" + - trusted-worker-port: + short: P + long: trusted-worker-port + help: Set the trusted websocket port of the worker, running directly in the enclave. + takes_value: true + default_value: "2000" + - untrusted-worker-port: + short: w + long: untrusted-worker-port + help: Set the untrusted websocket port of the worker + takes_value: true + default_value: "2001" + - trusted-external-address: + short: T + long: trusted-external-address + help: Set the trusted worker address to be advertised on the parentchain. If no port is given, the same as in `trusted-worker-port` will be used. + takes_value: true + required: false + - untrusted-external-address: + short: U + long: untrusted-external-address + help: Set the untrusted worker address to be retrieved by a trusted rpc call. If no port is given, the same as in `untrusted-worker-port` will be used. + takes_value: true + required: false + - mu-ra-external-address: + short: M + long: mu-ra-external-address + help: Set the mutual remote attestation worker address to be retrieved by a trusted rpc call. If no port is given, the same as in `mu-ra-port` will be used. + takes_value: true + required: false + - enable-metrics: + long: enable-metrics + help: Enable the metrics HTTP server to serve metrics + - metrics-port: + short: i + long: metrics-port + help: Set the port on which the metrics are served. + takes_value: true + default_value: "8787" + required: false + - untrusted-http-port: + short: h + long: untrusted-http-port + help: Set the port for the untrusted HTTP server + takes_value: true + required: false + - clean-reset: + long: clean-reset + short: c + help: Cleans and purges any previous state and key files and generates them anew before starting. + +subcommands: + - run: + about: Start the integritee-service + args: + - skip-ra: + long: skip-ra + short: s + help: skip remote attestation. Set this flag if running enclave in SW mode + - shard: + required: false + index: 1 + help: shard identifier base58 encoded. Defines the state that this worker shall operate on. Default is mrenclave + - dev: + long: dev + short: d + help: Set this flag if running in development mode to bootstrap enclave account on parentchain via //Alice. + - request-state: + long: request-state + short: r + help: Run the worker and request key and state provisioning from another worker. + - teeracle-interval: + required: false + long: teeracle-interval + short: i + help: Set the teeracle exchange rate update interval. Example of accepted syntax <5 seconds 15 minutes 2 hours 1 days> or short <5s15m2h1d> + takes_value: true + - request-state: + about: join a shard by requesting key provisioning from another worker + args: + - shard: + long: shard + short: s + required: false + help: shard identifier base58 encoded. Defines the state that this worker shall operate on. Default is mrenclave + - skip-ra: + long: skip-ra + short: s + help: skip remote attestation. Set this flag if running enclave in SW mode + - shielding-key: + about: Get the public RSA3072 key from the TEE to be used to encrypt requests + - signing-key: + about: Get the public ed25519 key the TEE uses to sign messages and extrinsics + - dump-ra: + about: Perform RA and dump cert to disk + - mrenclave: + about: Dump mrenclave to stdout. base58 encoded. + - init-shard: + about: Initialize new shard (do this only if you run the first worker for that shard). if shard is not specified, the MRENCLAVE is used instead + args: + - shard: + required: false + multiple: true + index: 1 + help: shard identifier base58 encoded + - test: + about: Run tests involving the enclave + takes_value: true + args: + - all: + short: a + long: all + help: Run all tests (beware, all corrupts the counter state for some whatever reason...) + takes_value: false + - unit: + short: u + long: unit + help: Run unit tests + takes_value: false + - ecall: + short: e + long: ecall + help: Run enclave ecall tests + takes_value: false + - integration: + short: i + long: integration + help: Run integration tests + takes_value: false + - provisioning-server: + long: provisioning-server + help: Run TEE server for MU-RA key provisioning + takes_value: false + - provisioning-client: + long: provisioning-client + help: Run TEE client for MU-RA key provisioning + takes_value: false diff --git a/tee-worker/service/src/config.rs b/tee-worker/service/src/config.rs new file mode 100644 index 0000000000..36365fd5d9 --- /dev/null +++ b/tee-worker/service/src/config.rs @@ -0,0 +1,449 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +use clap::ArgMatches; +use parse_duration::parse; +use serde::{Deserialize, Serialize}; +use std::time::Duration; + +static DEFAULT_NODE_SERVER: &str = "ws://127.0.0.1"; +static DEFAULT_NODE_PORT: &str = "9944"; +static DEFAULT_TRUSTED_PORT: &str = "2000"; +static DEFAULT_UNTRUSTED_PORT: &str = "2001"; +static DEFAULT_MU_RA_PORT: &str = "3443"; +static DEFAULT_METRICS_PORT: &str = "8787"; +static DEFAULT_UNTRUSTED_HTTP_PORT: &str = "4545"; + +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +pub struct Config { + pub node_ip: String, + pub node_port: String, + pub worker_ip: String, + /// Trusted worker address that will be advertised on the parentchain. + pub trusted_external_worker_address: Option, + /// Port to directly communicate with the trusted tls server inside the enclave. + pub trusted_worker_port: String, + /// Untrusted worker address that will be returned by the dedicated trusted ws rpc call. + pub untrusted_external_worker_address: Option, + /// Port to the untrusted ws of the validateer. + pub untrusted_worker_port: String, + /// Mutual remote attestation address that will be returned by the dedicated trusted ws rpc call. + pub mu_ra_external_address: Option, + /// Port for mutual-remote attestation requests. + pub mu_ra_port: String, + /// Enable the metrics server + pub enable_metrics_server: bool, + /// Port for the metrics server + pub metrics_server_port: String, + /// Port for the untrusted HTTP server (e.g. for `is_initialized`) + pub untrusted_http_port: String, + /// Config of the 'run' subcommand + pub run_config: Option, +} + +#[allow(clippy::too_many_arguments)] +impl Config { + pub fn new( + node_ip: String, + node_port: String, + worker_ip: String, + trusted_external_worker_address: Option, + trusted_worker_port: String, + untrusted_external_worker_address: Option, + untrusted_worker_port: String, + mu_ra_external_address: Option, + mu_ra_port: String, + enable_metrics_server: bool, + metrics_server_port: String, + untrusted_http_port: String, + run_config: Option, + ) -> Self { + Self { + node_ip, + node_port, + worker_ip, + trusted_external_worker_address, + trusted_worker_port, + untrusted_external_worker_address, + untrusted_worker_port, + mu_ra_external_address, + mu_ra_port, + enable_metrics_server, + metrics_server_port, + untrusted_http_port, + run_config, + } + } + + /// Returns the client url of the node (including ws://). + pub fn node_url(&self) -> String { + format!("{}:{}", self.node_ip, self.node_port) + } + + pub fn trusted_worker_url_internal(&self) -> String { + format!("{}:{}", self.worker_ip, self.trusted_worker_port) + } + + /// Returns the trusted worker url that should be addressed by external clients. + pub fn trusted_worker_url_external(&self) -> String { + match &self.trusted_external_worker_address { + Some(external_address) => external_address.to_string(), + None => format!("wss://{}:{}", self.worker_ip, self.trusted_worker_port), + } + } + + pub fn untrusted_worker_url(&self) -> String { + format!("{}:{}", self.worker_ip, self.untrusted_worker_port) + } + + /// Returns the untrusted worker url that should be addressed by external clients. + pub fn untrusted_worker_url_external(&self) -> String { + match &self.untrusted_external_worker_address { + Some(external_address) => external_address.to_string(), + None => format!("ws://{}:{}", self.worker_ip, self.untrusted_worker_port), + } + } + + pub fn mu_ra_url(&self) -> String { + format!("{}:{}", self.worker_ip, self.mu_ra_port) + } + + /// Returns the mutual remote attestion worker url that should be addressed by external workers. + pub fn mu_ra_url_external(&self) -> String { + match &self.mu_ra_external_address { + Some(external_address) => external_address.to_string(), + None => format!("{}:{}", self.worker_ip, self.mu_ra_port), + } + } + + pub fn try_parse_metrics_server_port(&self) -> Option { + self.metrics_server_port.parse::().ok() + } + + pub fn try_parse_untrusted_http_server_port(&self) -> Option { + self.untrusted_http_port.parse::().ok() + } +} + +impl From<&ArgMatches<'_>> for Config { + fn from(m: &ArgMatches<'_>) -> Self { + let trusted_port = m.value_of("trusted-worker-port").unwrap_or(DEFAULT_TRUSTED_PORT); + let untrusted_port = m.value_of("untrusted-worker-port").unwrap_or(DEFAULT_UNTRUSTED_PORT); + let mu_ra_port = m.value_of("mu-ra-port").unwrap_or(DEFAULT_MU_RA_PORT); + let is_metrics_server_enabled = m.is_present("enable-metrics"); + let metrics_server_port = m.value_of("metrics-port").unwrap_or(DEFAULT_METRICS_PORT); + let untrusted_http_port = + m.value_of("untrusted-http-port").unwrap_or(DEFAULT_UNTRUSTED_HTTP_PORT); + let run_config = m.subcommand_matches("run").map(RunConfig::from); + + Self::new( + m.value_of("node-server").unwrap_or(DEFAULT_NODE_SERVER).into(), + m.value_of("node-port").unwrap_or(DEFAULT_NODE_PORT).into(), + if m.is_present("ws-external") { "0.0.0.0".into() } else { "127.0.0.1".into() }, + m.value_of("trusted-external-address") + .map(|url| add_port_if_necessary(url, trusted_port)), + trusted_port.to_string(), + m.value_of("untrusted-external-address") + .map(|url| add_port_if_necessary(url, untrusted_port)), + untrusted_port.to_string(), + m.value_of("mu-ra-external-address") + .map(|url| add_port_if_necessary(url, mu_ra_port)), + mu_ra_port.to_string(), + is_metrics_server_enabled, + metrics_server_port.to_string(), + untrusted_http_port.to_string(), + run_config, + ) + } +} + +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +pub struct RunConfig { + /// Skip remote attestation. Set this flag if running enclave in SW mode + pub skip_ra: bool, + /// Set this flag if running in development mode to bootstrap enclave account on parentchain via //Alice. + pub dev: bool, + /// Request key and state provisioning from a peer worker. + pub request_state: bool, + /// Shard identifier base58 encoded. Defines the shard that this worker operates on. Default is mrenclave. + pub shard: Option, + /// Optional teeracle update interval + pub teeracle_update_interval: Option, +} + +impl From<&ArgMatches<'_>> for RunConfig { + fn from(m: &ArgMatches<'_>) -> Self { + let skip_ra = m.is_present("skip-ra"); + let dev = m.is_present("dev"); + let request_state = m.is_present("request-state"); + let shard = m.value_of("shard").map(|s| s.to_string()); + let teeracle_update_interval = m.value_of("teeracle-interval").map(|i| { + parse(i).unwrap_or_else(|e| panic!("teeracle-interval parsing error {:?}", e)) + }); + + Self { skip_ra, dev, request_state, shard, teeracle_update_interval } + } +} + +fn add_port_if_necessary(url: &str, port: &str) -> String { + // [Option("ws(s)"), ip, Option(port)] + match url.split(':').count() { + 3 => url.to_string(), + 2 => { + if url.contains("ws") { + // url is of format ws://127.0.0.1, no port added + format!("{}:{}", url, port) + } else { + // url is of format 127.0.0.1:4000, port was added + url.to_string() + } + }, + 1 => format!("{}:{}", url, port), + _ => panic!("Invalid worker url format in url input {:?}", url), + } +} + +#[cfg(test)] +mod test { + use super::*; + use std::collections::HashMap; + + #[test] + fn check_correct_config_assignment_for_empty_input() { + let empty_args = ArgMatches::default(); + let config = Config::from(&empty_args); + let expected_worker_ip = "127.0.0.1"; + + assert_eq!(config.node_ip, DEFAULT_NODE_SERVER); + assert_eq!(config.node_port, DEFAULT_NODE_PORT); + assert_eq!(config.trusted_worker_port, DEFAULT_TRUSTED_PORT); + assert_eq!(config.untrusted_worker_port, DEFAULT_UNTRUSTED_PORT); + assert_eq!(config.mu_ra_port, DEFAULT_MU_RA_PORT); + assert_eq!(config.worker_ip, expected_worker_ip); + assert!(config.trusted_external_worker_address.is_none()); + assert!(config.untrusted_external_worker_address.is_none()); + assert!(config.mu_ra_external_address.is_none()); + assert!(!config.enable_metrics_server); + assert_eq!(config.untrusted_http_port, DEFAULT_UNTRUSTED_HTTP_PORT); + assert!(config.run_config.is_none()); + } + + #[test] + fn worker_ip_is_set_correcty_for_set_ws_external_flag() { + let expected_worker_ip = "0.0.0.0"; + + let mut args = ArgMatches::default(); + args.args = HashMap::from([("ws-external", Default::default())]); + let config = Config::from(&args); + + assert_eq!(config.worker_ip, expected_worker_ip); + } + + #[test] + fn check_correct_config_assignment_for_given_input() { + let node_ip = "ws://12.1.58.1"; + let node_port = "111111"; + let trusted_ext_addr = "wss://1.1.1.2:700"; + let trusted_port = "7119"; + let untrusted_ext_addr = "ws://1.723.3.1:11"; + let untrusted_port = "9119"; + let mu_ra_ext_addr = "1.1.3.1:1000"; + let mu_ra_port = "99"; + let untrusted_http_port = "4321"; + + let mut args = ArgMatches::default(); + args.args = HashMap::from([ + ("node-server", Default::default()), + ("node-port", Default::default()), + ("ws-external", Default::default()), + ("trusted-external-address", Default::default()), + ("untrusted-external-address", Default::default()), + ("mu-ra-external-address", Default::default()), + ("mu-ra-port", Default::default()), + ("untrusted-worker-port", Default::default()), + ("trusted-worker-port", Default::default()), + ("untrusted-http-port", Default::default()), + ]); + // Workaround because MatchedArg is private. + args.args.get_mut("node-server").unwrap().vals = vec![node_ip.into()]; + args.args.get_mut("node-port").unwrap().vals = vec![node_port.into()]; + args.args.get_mut("trusted-external-address").unwrap().vals = vec![trusted_ext_addr.into()]; + args.args.get_mut("untrusted-external-address").unwrap().vals = + vec![untrusted_ext_addr.into()]; + args.args.get_mut("mu-ra-external-address").unwrap().vals = vec![mu_ra_ext_addr.into()]; + args.args.get_mut("mu-ra-port").unwrap().vals = vec![mu_ra_port.into()]; + args.args.get_mut("untrusted-worker-port").unwrap().vals = vec![untrusted_port.into()]; + args.args.get_mut("trusted-worker-port").unwrap().vals = vec![trusted_port.into()]; + args.args.get_mut("untrusted-http-port").unwrap().vals = vec![untrusted_http_port.into()]; + + let config = Config::from(&args); + + assert_eq!(config.node_ip, node_ip); + assert_eq!(config.node_port, node_port); + assert_eq!(config.trusted_worker_port, trusted_port); + assert_eq!(config.untrusted_worker_port, untrusted_port); + assert_eq!(config.mu_ra_port, mu_ra_port); + assert_eq!(config.trusted_external_worker_address, Some(trusted_ext_addr.to_string())); + assert_eq!(config.untrusted_external_worker_address, Some(untrusted_ext_addr.to_string())); + assert_eq!(config.mu_ra_external_address, Some(mu_ra_ext_addr.to_string())); + assert_eq!(config.untrusted_http_port, untrusted_http_port.to_string()); + } + + #[test] + fn default_run_config_is_correct() { + let empty_args = ArgMatches::default(); + let run_config = RunConfig::from(&empty_args); + + assert_eq!(run_config.request_state, false); + assert_eq!(run_config.dev, false); + assert_eq!(run_config.skip_ra, false); + assert!(run_config.shard.is_none()); + assert!(run_config.teeracle_update_interval.is_none()); + } + + #[test] + fn run_config_parsing_works() { + let shard_identifier = "shard-identifier"; + + let mut args = ArgMatches::default(); + args.args = HashMap::from([ + ("request-state", Default::default()), + ("dev", Default::default()), + ("skip-ra", Default::default()), + ("shard", Default::default()), + ("teeracle-interval", Default::default()), + ]); + // Workaround because MatchedArg is private. + args.args.get_mut("shard").unwrap().vals = vec![shard_identifier.into()]; + args.args.get_mut("teeracle-interval").unwrap().vals = vec!["42s".into()]; + + let run_config = RunConfig::from(&args); + + assert_eq!(run_config.request_state, true); + assert_eq!(run_config.dev, true); + assert_eq!(run_config.skip_ra, true); + assert_eq!(run_config.shard.unwrap(), shard_identifier.to_string()); + assert_eq!(run_config.teeracle_update_interval.unwrap(), Duration::from_secs(42)); + } + + #[test] + fn external_addresses_are_returned_correctly_if_not_set() { + let trusted_port = "7119"; + let untrusted_port = "9119"; + let mu_ra_port = "99"; + let expected_worker_ip = "127.0.0.1"; + + let mut args = ArgMatches::default(); + args.args = HashMap::from([ + ("mu-ra-port", Default::default()), + ("untrusted-worker-port", Default::default()), + ("trusted-worker-port", Default::default()), + ]); + // Workaround because MatchedArg is private. + args.args.get_mut("mu-ra-port").unwrap().vals = vec![mu_ra_port.into()]; + args.args.get_mut("untrusted-worker-port").unwrap().vals = vec![untrusted_port.into()]; + args.args.get_mut("trusted-worker-port").unwrap().vals = vec![trusted_port.into()]; + + let config = Config::from(&args); + + assert_eq!( + config.trusted_worker_url_external(), + format!("wss://{}:{}", expected_worker_ip, trusted_port) + ); + assert_eq!( + config.untrusted_worker_url_external(), + format!("ws://{}:{}", expected_worker_ip, untrusted_port) + ); + assert_eq!(config.mu_ra_url_external(), format!("{}:{}", expected_worker_ip, mu_ra_port)); + } + + #[test] + fn teeracle_interval_parsing_panics_if_format_is_invalid() { + let teeracle_interval = "24s_invalid-format"; + let mut args = ArgMatches::default(); + args.args = HashMap::from([("teeracle-interval", Default::default())]); + args.args.get_mut("teeracle-interval").unwrap().vals = vec![teeracle_interval.into()]; + + let result = std::panic::catch_unwind(|| RunConfig::from(&args)); + assert!(result.is_err()); + } + + #[test] + fn external_addresses_are_returned_correctly_if_set() { + let trusted_ext_addr = "wss://1.1.1.2:700"; + let untrusted_ext_addr = "ws://1.723.3.1:11"; + let mu_ra_ext_addr = "1.1.3.1:1000"; + + let mut args = ArgMatches::default(); + args.args = HashMap::from([ + ("trusted-external-address", Default::default()), + ("untrusted-external-address", Default::default()), + ("mu-ra-external-address", Default::default()), + ]); + // Workaround because MatchedArg is private. + args.args.get_mut("trusted-external-address").unwrap().vals = vec![trusted_ext_addr.into()]; + args.args.get_mut("untrusted-external-address").unwrap().vals = + vec![untrusted_ext_addr.into()]; + args.args.get_mut("mu-ra-external-address").unwrap().vals = vec![mu_ra_ext_addr.into()]; + + let config = Config::from(&args); + + assert_eq!(config.trusted_worker_url_external(), trusted_ext_addr); + assert_eq!(config.untrusted_worker_url_external(), untrusted_ext_addr); + assert_eq!(config.mu_ra_url_external(), mu_ra_ext_addr); + } + + #[test] + fn ensure_no_port_is_added_to_url_with_port() { + let url = "ws://hello:4000"; + let port = "0"; + + let resulting_url = add_port_if_necessary(url, port); + + assert_eq!(resulting_url, url); + } + + #[test] + fn ensure_port_is_added_to_url_without_port() { + let url = "wss://hello"; + let port = "0"; + + let resulting_url = add_port_if_necessary(url, port); + + assert_eq!(resulting_url, format!("{}:{}", url, port)); + } + + #[test] + fn ensure_no_port_is_added_to_url_with_port_without_prefix() { + let url = "hello:10001"; + let port = "012"; + + let resulting_url = add_port_if_necessary(url, port); + + assert_eq!(resulting_url, url); + } + + #[test] + fn ensure_port_is_added_to_url_without_port_without_prefix() { + let url = "hello_world"; + let port = "10"; + + let resulting_url = add_port_if_necessary(url, port); + + assert_eq!(resulting_url, format!("{}:{}", url, port)); + } +} diff --git a/tee-worker/service/src/enclave/api.rs b/tee-worker/service/src/enclave/api.rs new file mode 100644 index 0000000000..48a900c89b --- /dev/null +++ b/tee-worker/service/src/enclave/api.rs @@ -0,0 +1,109 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +use crate::config::Config; +use itp_enclave_api::{ + enclave_base::EnclaveBase, error::Error as EnclaveApiError, Enclave, EnclaveResult, +}; +use itp_settings::files::{ENCLAVE_FILE, ENCLAVE_TOKEN}; +use log::*; +use sgx_types::*; +use sgx_urts::SgxEnclave; +/// keep this api free from chain-specific types! +use std::io::{Read, Write}; +use std::{fs::File, path::PathBuf}; + +pub fn enclave_init(config: &Config) -> EnclaveResult { + const LEN: usize = 1024; + let mut launch_token = [0; LEN]; + let mut launch_token_updated = 0; + + // Step 1: try to retrieve the launch token saved by last transaction + // if there is no token, then create a new one. + // + // try to get the token saved in $HOME */ + let mut home_dir = PathBuf::new(); + let use_token = match dirs::home_dir() { + Some(path) => { + info!("[+] Home dir is {}", path.display()); + home_dir = path; + true + }, + None => { + error!("[-] Cannot get home dir"); + false + }, + }; + let token_file = home_dir.join(ENCLAVE_TOKEN); + if use_token { + match File::open(&token_file) { + Err(_) => { + info!( + "[-] Token file {} not found! Will create one.", + token_file.as_path().to_str().unwrap() + ); + }, + Ok(mut f) => { + info!("[+] Open token file success! "); + match f.read(&mut launch_token) { + Ok(LEN) => { + info!("[+] Token file valid!"); + }, + _ => info!("[+] Token file invalid, will create new token file"), + } + }, + } + } + + // Step 2: call sgx_create_enclave to initialize an enclave instance + // Debug Support: 1 = debug mode, 0 = not debug mode + #[cfg(not(feature = "production"))] + let debug = 1; + #[cfg(feature = "production")] + let debug = 0; + + let mut misc_attr = + sgx_misc_attribute_t { secs_attr: sgx_attributes_t { flags: 0, xfrm: 0 }, misc_select: 0 }; + let enclave = (SgxEnclave::create( + ENCLAVE_FILE, + debug, + &mut launch_token, + &mut launch_token_updated, + &mut misc_attr, + )) + .map_err(EnclaveApiError::Sgx)?; + + // Step 3: save the launch token if it is updated + if use_token && launch_token_updated != 0 { + // reopen the file with write capability + match File::create(&token_file) { + Ok(mut f) => match f.write_all(&launch_token) { + Ok(()) => info!("[+] Saved updated launch token!"), + Err(_) => error!("[-] Failed to save updated launch token!"), + }, + Err(_) => { + warn!("[-] Failed to save updated enclave token, but doesn't matter"); + }, + } + } + + // create an enclave API and initialize it + let enclave_api = Enclave::new(enclave); + enclave_api.init(&config.mu_ra_url_external(), &config.untrusted_worker_url_external())?; + + Ok(enclave_api) +} diff --git a/tee-worker/service/src/enclave/mod.rs b/tee-worker/service/src/enclave/mod.rs new file mode 100644 index 0000000000..e9e8984207 --- /dev/null +++ b/tee-worker/service/src/enclave/mod.rs @@ -0,0 +1,19 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +pub mod api; +pub mod tls_ra; diff --git a/tee-worker/service/src/enclave/tls_ra.rs b/tee-worker/service/src/enclave/tls_ra.rs new file mode 100644 index 0000000000..c618c78ef9 --- /dev/null +++ b/tee-worker/service/src/enclave/tls_ra.rs @@ -0,0 +1,76 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ +use itp_enclave_api::{error::Error, remote_attestation::TlsRemoteAttestation, EnclaveResult}; +use itp_types::ShardIdentifier; +use log::*; +use sgx_types::*; +use std::{ + net::{TcpListener, TcpStream}, + os::unix::io::AsRawFd, +}; + +pub fn enclave_run_state_provisioning_server( + enclave_api: &E, + sign_type: sgx_quote_sign_type_t, + addr: &str, + skip_ra: bool, +) { + info!("Starting MU-RA-Server on: {}", addr); + let listener = match TcpListener::bind(addr) { + Ok(l) => l, + Err(e) => { + error!("error starting MU-RA server on {}: {}", addr, e); + return + }, + }; + loop { + match listener.accept() { + Ok((socket, addr)) => { + info!("[MU-RA-Server] a worker at {} is requesting key provisiong", addr); + + let result = enclave_api.run_state_provisioning_server( + socket.as_raw_fd(), + sign_type, + skip_ra, + ); + + match result { + Ok(_) => { + debug!("[MU-RA-Server] ECALL success!"); + }, + Err(e) => { + error!("[MU-RA-Server] ECALL Enclave Failed {:?}!", e); + }, + } + }, + Err(e) => error!("couldn't get client: {:?}", e), + } + } +} + +pub fn enclave_request_state_provisioning( + enclave_api: &E, + sign_type: sgx_quote_sign_type_t, + addr: &str, + shard: &ShardIdentifier, + skip_ra: bool, +) -> EnclaveResult<()> { + info!("[MU-RA-Client] Requesting key provisioning from {}", addr); + + let stream = TcpStream::connect(addr).map_err(|e| Error::Other(Box::new(e)))?; + enclave_api.request_state_provisioning(stream.as_raw_fd(), sign_type, shard, skip_ra) +} diff --git a/tee-worker/service/src/error.rs b/tee-worker/service/src/error.rs new file mode 100644 index 0000000000..8dea96c1d1 --- /dev/null +++ b/tee-worker/service/src/error.rs @@ -0,0 +1,55 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +use codec::Error as CodecError; +use itp_types::ShardIdentifier; +use substrate_api_client::ApiClientError; + +pub type ServiceResult = Result; + +#[derive(Debug, thiserror::Error)] +pub enum Error { + #[error("{0}")] + Codec(#[from] CodecError), + #[error("{0}")] + ApiClient(#[from] ApiClientError), + #[error("Node API terminated subscription unexpectedly: {0}")] + ApiSubscriptionDisconnected(#[from] std::sync::mpsc::RecvError), + #[error("Enclave API error: {0}")] + EnclaveApi(#[from] itp_enclave_api::error::Error), + #[error("Trusted Rpc Client error: {0}")] + TrustedRpcClient(#[from] itc_rpc_client::error::Error), + #[error("{0}")] + JsonRpSeeClient(#[from] jsonrpsee::types::Error), + #[error("{0}")] + Serialization(#[from] serde_json::Error), + #[error("{0}")] + FromUtf8(#[from] std::string::FromUtf8Error), + #[error("Application setup error!")] + ApplicationSetup, + #[error("Failed to find any peer worker")] + NoPeerWorkerFound, + #[error("No worker for shard {0} found on parentchain")] + NoWorkerForShardFound(ShardIdentifier), + #[error("Returned empty parentchain block vec after sync, even though there have been blocks given as input")] + EmptyChunk, + #[error("Could not find genesis header of the parentchain")] + MissingGenesisHeader, + #[error("Could not find last finalized block of the parentchain")] + MissingLastFinalizedBlock, + #[error("{0}")] + Custom(Box), +} diff --git a/tee-worker/service/src/globals/mod.rs b/tee-worker/service/src/globals/mod.rs new file mode 100644 index 0000000000..ee250661c5 --- /dev/null +++ b/tee-worker/service/src/globals/mod.rs @@ -0,0 +1,19 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + Copyright (C) 2017-2019 Baidu, Inc. All Rights Reserved. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +pub mod tokio_handle; diff --git a/tee-worker/service/src/globals/tokio_handle.rs b/tee-worker/service/src/globals/tokio_handle.rs new file mode 100644 index 0000000000..54e49d985e --- /dev/null +++ b/tee-worker/service/src/globals/tokio_handle.rs @@ -0,0 +1,108 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + Copyright (C) 2017-2019 Baidu, Inc. All Rights Reserved. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +use lazy_static::lazy_static; +use parking_lot::RwLock; +use tokio::runtime::Handle; + +lazy_static! { + static ref TOKIO_HANDLE: RwLock> = RwLock::new(None); +} + +/// Wrapper for accessing a tokio handle +pub trait GetTokioHandle { + fn get_handle(&self) -> Handle; +} + +/// implementation, using a static global variable internally +/// +pub struct GlobalTokioHandle; + +/// these are the static (global) accessors +/// reduce their usage where possible and use an instance of TokioHandleAccessorImpl or the trait +impl GlobalTokioHandle { + /// this needs to be called once at application startup! + pub fn initialize() { + let rt = tokio::runtime::Builder::new_multi_thread() + .enable_all() + .worker_threads(2) + .build() + .unwrap(); + *TOKIO_HANDLE.write() = Some(rt); + } + + /// static / global getter of the handle (try to keep private!, use trait to access handle) + fn read_handle() -> Handle { + TOKIO_HANDLE + .read() + .as_ref() + .expect("Tokio handle has not been initialized!") + .handle() + .clone() + } +} + +impl GetTokioHandle for GlobalTokioHandle { + fn get_handle(&self) -> Handle { + GlobalTokioHandle::read_handle() + } +} + +/// Implementation for a scoped Tokio handle. +/// +/// +pub struct ScopedTokioHandle { + tokio_runtime: tokio::runtime::Runtime, +} + +impl Default for ScopedTokioHandle { + fn default() -> Self { + ScopedTokioHandle { tokio_runtime: tokio::runtime::Runtime::new().unwrap() } + } +} + +impl GetTokioHandle for ScopedTokioHandle { + fn get_handle(&self) -> Handle { + self.tokio_runtime.handle().clone() + } +} + +#[cfg(test)] +mod tests { + + use super::*; + + #[tokio::test] + async fn given_initialized_tokio_handle_when_runtime_goes_out_of_scope_then_async_handle_is_valid( + ) { + // initialize the global handle + // be aware that if you write more tests here, the global state will be shared across multiple threads + // which cargo test spawns. So it can lead to failing tests. + // solution: either get rid of the global state, or write all test functionality in this single test function + { + GlobalTokioHandle::initialize(); + } + + let handle = GlobalTokioHandle.get_handle(); + + let result = handle.spawn_blocking(|| "now running on a worker thread").await; + + assert!(result.is_ok()); + assert!(!result.unwrap().is_empty()) + } +} diff --git a/tee-worker/service/src/initialized_service.rs b/tee-worker/service/src/initialized_service.rs new file mode 100644 index 0000000000..f35a17fa28 --- /dev/null +++ b/tee-worker/service/src/initialized_service.rs @@ -0,0 +1,172 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +//! Service to determine if the integritee services is initialized and registered on the node, +//! hosted on a http server. + +use crate::error::ServiceResult; +use itp_settings::worker_mode::{ProvideWorkerMode, WorkerMode}; +use log::*; +use parking_lot::RwLock; +use std::{default::Default, marker::PhantomData, net::SocketAddr, sync::Arc}; +use warp::Filter; + +pub async fn start_is_initialized_server( + initialization_handler: Arc, + port: u16, +) -> ServiceResult<()> +where + Handler: IsInitialized + Send + Sync + 'static, +{ + let is_initialized_route = warp::path!("is_initialized").and_then(move || { + let handler_clone = initialization_handler.clone(); + async move { + if handler_clone.is_initialized() { + Ok("I am initialized.") + } else { + Err(warp::reject::not_found()) + } + } + }); + + let socket_addr: SocketAddr = ([0, 0, 0, 0], port).into(); + + info!("Running initialized server on: {:?}", socket_addr); + warp::serve(is_initialized_route).run(socket_addr).await; + + info!("Initialized server shut down"); + Ok(()) +} + +/// Trait to query of a worker is considered fully initialized. +pub trait IsInitialized { + fn is_initialized(&self) -> bool; +} + +/// Tracker for initialization. Used by components that ensure these steps were taken. +pub trait TrackInitialization { + fn registered_on_parentchain(&self); + + fn sidechain_block_produced(&self); + + fn worker_for_shard_registered(&self); +} + +pub struct InitializationHandler { + registered_on_parentchain: RwLock, + sidechain_block_produced: RwLock, + worker_for_shard_registered: RwLock, + _phantom: PhantomData, +} + +// Cannot use #[derive(Default)], because the compiler complains that WorkerModeProvider then +// also needs to implement Default. Which does not make sense, since it's only used in PhantomData. +// Explicitly implementing Default solves the problem +// (see https://stackoverflow.com/questions/59538071/the-trait-bound-t-stddefaultdefault-is-not-satisfied-when-using-phantomda). +impl Default for InitializationHandler { + fn default() -> Self { + Self { + registered_on_parentchain: Default::default(), + sidechain_block_produced: Default::default(), + worker_for_shard_registered: Default::default(), + _phantom: Default::default(), + } + } +} + +impl TrackInitialization for InitializationHandler { + fn registered_on_parentchain(&self) { + let mut registered_lock = self.registered_on_parentchain.write(); + *registered_lock = true; + } + + fn sidechain_block_produced(&self) { + let mut block_produced_lock = self.sidechain_block_produced.write(); + *block_produced_lock = true; + } + + fn worker_for_shard_registered(&self) { + let mut registered_lock = self.worker_for_shard_registered.write(); + *registered_lock = true; + } +} + +impl IsInitialized for InitializationHandler +where + WorkerModeProvider: ProvideWorkerMode, +{ + fn is_initialized(&self) -> bool { + match WorkerModeProvider::worker_mode() { + WorkerMode::Sidechain => + *self.registered_on_parentchain.read() + && *self.worker_for_shard_registered.read() + && *self.sidechain_block_produced.read(), + _ => *self.registered_on_parentchain.read(), + } + } +} + +#[cfg(test)] +mod tests { + + use super::*; + + struct OffchainWorkerMode; + impl ProvideWorkerMode for OffchainWorkerMode { + fn worker_mode() -> WorkerMode { + WorkerMode::OffChainWorker + } + } + + struct SidechainWorkerMode; + impl ProvideWorkerMode for SidechainWorkerMode { + fn worker_mode() -> WorkerMode { + WorkerMode::Sidechain + } + } + + #[test] + fn default_handler_is_initialized_returns_false() { + let offchain_worker_handler = InitializationHandler::::default(); + let sidechain_handler = InitializationHandler::::default(); + + assert!(!offchain_worker_handler.is_initialized()); + assert!(!sidechain_handler.is_initialized()); + } + + #[test] + fn in_offchain_worker_mode_parentchain_registration_is_enough_for_initialized() { + let initialization_handler = InitializationHandler::::default(); + initialization_handler.registered_on_parentchain(); + + assert!(initialization_handler.is_initialized()); + } + + #[test] + fn in_sidechain_mode_all_condition_have_to_be_met() { + let sidechain_handler = InitializationHandler::::default(); + + sidechain_handler.registered_on_parentchain(); + assert!(!sidechain_handler.is_initialized()); + + sidechain_handler.worker_for_shard_registered(); + assert!(!sidechain_handler.is_initialized()); + + sidechain_handler.sidechain_block_produced(); + assert!(sidechain_handler.is_initialized()); + } +} diff --git a/tee-worker/service/src/main.rs b/tee-worker/service/src/main.rs new file mode 100644 index 0000000000..a7fa56ef5e --- /dev/null +++ b/tee-worker/service/src/main.rs @@ -0,0 +1,750 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +#![cfg_attr(test, feature(assert_matches))] + +#[cfg(feature = "teeracle")] +use crate::teeracle::start_interval_market_update; + +use crate::{ + account_funding::{setup_account_funding, EnclaveAccountInfoProvider}, + error::Error, + globals::tokio_handle::{GetTokioHandle, GlobalTokioHandle}, + initialized_service::{ + start_is_initialized_server, InitializationHandler, IsInitialized, TrackInitialization, + }, + ocall_bridge::{ + bridge_api::Bridge as OCallBridge, component_factory::OCallBridgeComponentFactory, + }, + parentchain_handler::{HandleParentchain, ParentchainHandler}, + prometheus_metrics::{start_metrics_server, EnclaveMetricsReceiver, MetricsHandler}, + sidechain_setup::{sidechain_init_block_production, sidechain_start_untrusted_rpc_server}, + sync_block_broadcaster::SyncBlockBroadcaster, + utils::{check_files, extract_shard}, + worker::Worker, + worker_peers_updater::WorkerPeersUpdater, +}; +use base58::ToBase58; +use clap::{load_yaml, App}; +use codec::{Decode, Encode}; +use config::Config; +use enclave::{ + api::enclave_init, + tls_ra::{enclave_request_state_provisioning, enclave_run_state_provisioning_server}, +}; +use itp_enclave_api::{ + direct_request::DirectRequest, + enclave_base::EnclaveBase, + remote_attestation::{RemoteAttestation, TlsRemoteAttestation}, + sidechain::Sidechain, + stf_task_handler::StfTaskHandler, + teeracle_api::TeeracleApi, + Enclave, +}; +use itp_node_api::{ + api_client::{AccountApi, PalletTeerexApi, ParentchainApi}, + metadata::NodeMetadata, + node_api_factory::{CreateNodeApi, NodeApiFactory}, +}; +use itp_settings::{ + files::SIDECHAIN_STORAGE_PATH, + worker_mode::{ProvideWorkerMode, WorkerMode, WorkerModeProvider}, +}; +use its_peer_fetch::{ + block_fetch_client::BlockFetcher, untrusted_peer_fetch::UntrustedPeerFetcher, +}; +use its_primitives::types::block::SignedBlock as SignedSidechainBlock; +use its_storage::{interface::FetchBlocks, BlockPruner, SidechainStorageLock}; +use log::*; +use my_node_runtime::{Event, Hash, Header}; +use sgx_types::*; +use sp_core::crypto::{AccountId32, Ss58Codec}; +use sp_keyring::AccountKeyring; +use std::{ + path::PathBuf, + str, + sync::{ + mpsc::{channel, Sender}, + Arc, + }, + thread, + time::Duration, +}; +use substrate_api_client::{utils::FromHexString, Header as HeaderTrait, XtStatus}; +use teerex_primitives::ShardIdentifier; + +mod account_funding; +mod config; +mod enclave; +mod error; +mod globals; +mod initialized_service; +mod ocall_bridge; +mod parentchain_handler; +mod prometheus_metrics; +mod setup; +mod sidechain_setup; +mod sync_block_broadcaster; +mod sync_state; +#[cfg(feature = "teeracle")] +mod teeracle; +mod tests; +mod utils; +mod worker; +mod worker_peers_updater; + +const VERSION: &str = env!("CARGO_PKG_VERSION"); + +pub type EnclaveWorker = + Worker>; + +fn main() { + // Setup logging + env_logger::init(); + + let yml = load_yaml!("cli.yml"); + let matches = App::from_yaml(yml).get_matches(); + + let config = Config::from(&matches); + + GlobalTokioHandle::initialize(); + + // log this information, don't println because some python scripts for GA rely on the + // stdout from the service + #[cfg(feature = "production")] + info!("*** Starting service in SGX production mode"); + #[cfg(not(feature = "production"))] + info!("*** Starting service in SGX debug mode"); + + info!("*** Running worker in mode: {:?} \n", WorkerModeProvider::worker_mode()); + + #[cfg(feature = "mockserver")] + thread::spawn(move || { + info!("*** Starting mock server"); + lc_mock_server::run(); + }); + + let clean_reset = matches.is_present("clean-reset"); + if clean_reset { + setup::purge_files_from_cwd().unwrap(); + } + + // build the entire dependency tree + let tokio_handle = Arc::new(GlobalTokioHandle {}); + let sidechain_blockstorage = Arc::new( + SidechainStorageLock::::new(PathBuf::from(&SIDECHAIN_STORAGE_PATH)) + .unwrap(), + ); + let node_api_factory = + Arc::new(NodeApiFactory::new(config.node_url(), AccountKeyring::Alice.pair())); + let enclave = Arc::new(enclave_init(&config).unwrap()); + let initialization_handler = Arc::new(InitializationHandler::default()); + let worker = Arc::new(EnclaveWorker::new( + config.clone(), + enclave.clone(), + node_api_factory.clone(), + initialization_handler.clone(), + Vec::new(), + )); + let sync_block_broadcaster = + Arc::new(SyncBlockBroadcaster::new(tokio_handle.clone(), worker.clone())); + let peer_updater = Arc::new(WorkerPeersUpdater::new(worker)); + let untrusted_peer_fetcher = UntrustedPeerFetcher::new(node_api_factory.clone()); + let peer_sidechain_block_fetcher = + Arc::new(BlockFetcher::::new(untrusted_peer_fetcher)); + let enclave_metrics_receiver = Arc::new(EnclaveMetricsReceiver {}); + + // initialize o-call bridge with a concrete factory implementation + OCallBridge::initialize(Arc::new(OCallBridgeComponentFactory::new( + node_api_factory.clone(), + sync_block_broadcaster, + enclave.clone(), + sidechain_blockstorage.clone(), + peer_updater, + peer_sidechain_block_fetcher, + tokio_handle.clone(), + enclave_metrics_receiver, + ))); + + if let Some(run_config) = &config.run_config { + let shard = extract_shard(&run_config.shard, enclave.as_ref()); + + println!("Worker Config: {:?}", config); + + if clean_reset { + setup::initialize_shard_and_keys(enclave.as_ref(), &shard).unwrap(); + } + + let node_api = + node_api_factory.create_api().expect("Failed to create parentchain node API"); + + if run_config.request_state { + sync_state::sync_state::<_, _, WorkerModeProvider>( + &node_api, + &shard, + enclave.as_ref(), + run_config.skip_ra, + ); + } + + start_worker::<_, _, _, _, WorkerModeProvider>( + config, + &shard, + enclave, + sidechain_blockstorage, + node_api, + tokio_handle, + initialization_handler, + ); + } else if let Some(smatches) = matches.subcommand_matches("request-state") { + println!("*** Requesting state from a registered worker \n"); + let node_api = + node_api_factory.create_api().expect("Failed to create parentchain node API"); + sync_state::sync_state::<_, _, WorkerModeProvider>( + &node_api, + &extract_shard(&smatches.value_of("shard").map(|s| s.to_string()), enclave.as_ref()), + enclave.as_ref(), + smatches.is_present("skip-ra"), + ); + } else if matches.is_present("shielding-key") { + setup::generate_shielding_key_file(enclave.as_ref()); + } else if matches.is_present("signing-key") { + setup::generate_signing_key_file(enclave.as_ref()); + } else if matches.is_present("dump-ra") { + info!("*** Perform RA and dump cert to disk"); + enclave.dump_ra_to_disk().unwrap(); + } else if matches.is_present("mrenclave") { + println!("{}", enclave.get_mrenclave().unwrap().encode().to_base58()); + } else if let Some(sub_matches) = matches.subcommand_matches("init-shard") { + setup::init_shard( + enclave.as_ref(), + &extract_shard(&sub_matches.value_of("shard").map(|s| s.to_string()), enclave.as_ref()), + ); + } else if let Some(sub_matches) = matches.subcommand_matches("test") { + if sub_matches.is_present("provisioning-server") { + println!("*** Running Enclave MU-RA TLS server\n"); + enclave_run_state_provisioning_server( + enclave.as_ref(), + sgx_quote_sign_type_t::SGX_UNLINKABLE_SIGNATURE, + &config.mu_ra_url(), + sub_matches.is_present("skip-ra"), + ); + println!("[+] Done!"); + } else if sub_matches.is_present("provisioning-client") { + println!("*** Running Enclave MU-RA TLS client\n"); + let shard = extract_shard( + &sub_matches.value_of("shard").map(|s| s.to_string()), + enclave.as_ref(), + ); + enclave_request_state_provisioning( + enclave.as_ref(), + sgx_quote_sign_type_t::SGX_UNLINKABLE_SIGNATURE, + &config.mu_ra_url_external(), + &shard, + sub_matches.is_present("skip-ra"), + ) + .unwrap(); + println!("[+] Done!"); + } else { + tests::run_enclave_tests(sub_matches); + } + } else { + println!("For options: use --help"); + } +} + +/// FIXME: needs some discussion (restructuring?) +#[allow(clippy::too_many_arguments)] +fn start_worker( + config: Config, + shard: &ShardIdentifier, + enclave: Arc, + sidechain_storage: Arc, + node_api: ParentchainApi, + tokio_handle_getter: Arc, + initialization_handler: Arc, +) where + T: GetTokioHandle, + E: EnclaveBase + + DirectRequest + + Sidechain + + RemoteAttestation + + TlsRemoteAttestation + + TeeracleApi + + StfTaskHandler + + Clone, + D: BlockPruner + FetchBlocks + Sync + Send + 'static, + InitializationHandler: TrackInitialization + IsInitialized + Sync + Send + 'static, + WorkerModeProvider: ProvideWorkerMode, +{ + println!("Integritee Worker v{}", VERSION); + info!("starting worker on shard {}", shard.encode().to_base58()); + // ------------------------------------------------------------------------ + // check for required files + check_files(); + // ------------------------------------------------------------------------ + // initialize the enclave + let mrenclave = enclave.get_mrenclave().unwrap(); + println!("MRENCLAVE={}", mrenclave.to_base58()); + println!("MRENCLAVE in hex {:?}", hex::encode(mrenclave)); + + // ------------------------------------------------------------------------ + // let new workers call us for key provisioning + println!("MU-RA server listening on {}", config.mu_ra_url()); + let run_config = config.run_config.clone().expect("Run config missing"); + let skip_ra = run_config.skip_ra; + let is_development_mode = run_config.dev; + let ra_url = config.mu_ra_url(); + let enclave_api_key_prov = enclave.clone(); + thread::spawn(move || { + enclave_run_state_provisioning_server( + enclave_api_key_prov.as_ref(), + sgx_quote_sign_type_t::SGX_UNLINKABLE_SIGNATURE, + &ra_url, + skip_ra, + ); + info!("State provisioning server stopped."); + }); + + let tokio_handle = tokio_handle_getter.get_handle(); + + #[cfg(feature = "teeracle")] + let teeracle_tokio_handle = tokio_handle.clone(); + + // ------------------------------------------------------------------------ + // Get the public key of our TEE. + let tee_accountid = enclave_account(enclave.as_ref()); + println!("Enclave account {:} ", &tee_accountid.to_ss58check()); + + // ------------------------------------------------------------------------ + // Start `is_initialized` server. + let untrusted_http_server_port = config + .try_parse_untrusted_http_server_port() + .expect("untrusted http server port to be a valid port number"); + let initialization_handler_clone = initialization_handler.clone(); + tokio_handle.spawn(async move { + if let Err(e) = + start_is_initialized_server(initialization_handler_clone, untrusted_http_server_port) + .await + { + error!("Unexpected error in `is_initialized` server: {:?}", e); + } + }); + + // ------------------------------------------------------------------------ + // Start prometheus metrics server. + if config.enable_metrics_server { + let enclave_wallet = + Arc::new(EnclaveAccountInfoProvider::new(node_api.clone(), tee_accountid.clone())); + let metrics_handler = Arc::new(MetricsHandler::new(enclave_wallet)); + let metrics_server_port = config + .try_parse_metrics_server_port() + .expect("metrics server port to be a valid port number"); + tokio_handle.spawn(async move { + if let Err(e) = start_metrics_server(metrics_handler, metrics_server_port).await { + error!("Unexpected error in Prometheus metrics server: {:?}", e); + } + }); + } + + // ------------------------------------------------------------------------ + // Start trusted worker rpc server + if WorkerModeProvider::worker_mode() == WorkerMode::Sidechain + || WorkerModeProvider::worker_mode() == WorkerMode::OffChainWorker + { + let direct_invocation_server_addr = config.trusted_worker_url_internal(); + let enclave_for_direct_invocation = enclave.clone(); + thread::spawn(move || { + println!( + "[+] Trusted RPC direct invocation server listening on {}", + direct_invocation_server_addr + ); + enclave_for_direct_invocation + .init_direct_invocation_server(direct_invocation_server_addr) + .unwrap(); + println!("[+] RPC direct invocation server shut down"); + }); + } + + // ------------------------------------------------------------------------ + // Start untrusted worker rpc server. + // i.e move sidechain block importing to trusted worker. + if WorkerModeProvider::worker_mode() == WorkerMode::Sidechain { + sidechain_start_untrusted_rpc_server( + &config, + enclave.clone(), + sidechain_storage.clone(), + tokio_handle, + ); + } + + // ------------------------------------------------------------------------ + // Init parentchain specific stuff. Needed for parentchain communication. + let parentchain_handler = Arc::new( + ParentchainHandler::new_with_automatic_light_client_allocation( + node_api.clone(), + enclave.clone(), + ) + .unwrap(), + ); + let last_synced_header = parentchain_handler.init_parentchain_components().unwrap(); + let nonce = node_api.get_nonce_of(&tee_accountid).unwrap(); + info!("Enclave nonce = {:?}", nonce); + enclave + .set_nonce(nonce) + .expect("Could not set nonce of enclave. Returning here..."); + + let metadata = node_api.metadata.clone(); + let runtime_spec_version = node_api.runtime_version.spec_version; + let runtime_transaction_version = node_api.runtime_version.transaction_version; + enclave + .set_node_metadata( + NodeMetadata::new(metadata, runtime_spec_version, runtime_transaction_version).encode(), + ) + .expect("Could not set the node metadata in the enclave"); + + // ------------------------------------------------------------------------ + // Perform a remote attestation and get an unchecked extrinsic back. + let trusted_url = config.trusted_worker_url_external(); + if skip_ra { + println!( + "[!] skipping remote attestation. Registering enclave without attestation report." + ); + } else { + println!("[!] creating remote attestation report and create enclave register extrinsic."); + }; + let uxt = enclave.perform_ra(&trusted_url, skip_ra).unwrap(); + + let mut xthex = hex::encode(uxt); + xthex.insert_str(0, "0x"); + + // Account funds + if let Err(x) = + setup_account_funding(&node_api, &tee_accountid, xthex.clone(), is_development_mode) + { + error!("Starting worker failed: {:?}", x); + // Return without registering the enclave. This will fail and the transaction will be banned for 30min. + return + } + + println!("[>] Register the enclave (send the extrinsic)"); + let register_enclave_xt_hash = node_api.send_extrinsic(xthex, XtStatus::Finalized).unwrap(); + println!("[<] Extrinsic got finalized. Hash: {:?}\n", register_enclave_xt_hash); + + let register_enclave_xt_header = + node_api.get_header(register_enclave_xt_hash).unwrap().unwrap(); + + let we_are_primary_validateer = + we_are_primary_validateer(&node_api, ®ister_enclave_xt_header).unwrap(); + + if we_are_primary_validateer { + println!("[+] We are the primary validateer"); + } else { + println!("[+] We are NOT the primary validateer"); + } + + initialization_handler.registered_on_parentchain(); + + // ------------------------------------------------------------------------ + // Start stf task handler thread + let enclave_api_stf_task_handler = enclave.clone(); + thread::spawn(move || { + enclave_api_stf_task_handler.run_stf_task_handler().unwrap(); + }); + + // ------------------------------------------------------------------------ + // initialize teeracle interval + #[cfg(feature = "teeracle")] + if WorkerModeProvider::worker_mode() == WorkerMode::Teeracle { + start_interval_market_update( + &node_api, + run_config.teeracle_update_interval, + enclave.as_ref(), + &teeracle_tokio_handle, + ); + } + + if WorkerModeProvider::worker_mode() != WorkerMode::Teeracle { + println!("*** [+] Finished syncing light client, syncing parentchain..."); + + // Syncing all parentchain blocks, this might take a while.. + let mut last_synced_header = + parentchain_handler.sync_parentchain(last_synced_header).unwrap(); + + // ------------------------------------------------------------------------ + // Initialize the sidechain + if WorkerModeProvider::worker_mode() == WorkerMode::Sidechain { + last_synced_header = sidechain_init_block_production( + enclave, + ®ister_enclave_xt_header, + we_are_primary_validateer, + parentchain_handler.clone(), + sidechain_storage, + &last_synced_header, + ) + .unwrap(); + } + + // ------------------------------------------------------------------------ + // start parentchain syncing loop (subscribe to header updates) + thread::Builder::new() + .name("parentchain_sync_loop".to_owned()) + .spawn(move || { + if let Err(e) = + subscribe_to_parentchain_new_headers(parentchain_handler, last_synced_header) + { + error!("Parentchain block syncing terminated with a failure: {:?}", e); + } + println!("[!] Parentchain block syncing has terminated"); + }) + .unwrap(); + } + + // ------------------------------------------------------------------------ + if WorkerModeProvider::worker_mode() == WorkerMode::Sidechain { + spawn_worker_for_shard_polling(shard, node_api.clone(), initialization_handler); + } + + // ------------------------------------------------------------------------ + // subscribe to events and react on firing + println!("*** Subscribing to events"); + let (sender, receiver) = channel(); + let sender2 = sender.clone(); + let _eventsubscriber = thread::Builder::new() + .name("eventsubscriber".to_owned()) + .spawn(move || { + node_api.subscribe_events(sender2).unwrap(); + }) + .unwrap(); + + println!("[+] Subscribed to events. waiting..."); + let timeout = Duration::from_millis(10); + loop { + if let Ok(msg) = receiver.recv_timeout(timeout) { + if let Ok(events) = parse_events(msg.clone()) { + print_events(events, sender.clone()) + } + } + } +} + +/// Start polling loop to wait until we have a worker for a shard registered on +/// the parentchain (TEEREX WorkerForShard). This is the pre-requisite to be +/// considered initialized and ready for the next worker to start (in sidechain mode only). +/// considered initialized and ready for the next worker to start. +fn spawn_worker_for_shard_polling( + shard: &ShardIdentifier, + node_api: ParentchainApi, + initialization_handler: Arc, +) where + InitializationHandler: TrackInitialization + Sync + Send + 'static, +{ + let shard_for_initialized = *shard; + thread::spawn(move || { + const POLL_INTERVAL_SECS: u64 = 2; + + loop { + info!("Polling for worker for shard ({} seconds interval)", POLL_INTERVAL_SECS); + if let Ok(Some(_)) = node_api.worker_for_shard(&shard_for_initialized, None) { + // Set that the service is initialized. + initialization_handler.worker_for_shard_registered(); + println!("[+] Found `WorkerForShard` on parentchain state"); + break + } + thread::sleep(Duration::from_secs(POLL_INTERVAL_SECS)); + } + }); +} + +type Events = Vec>; + +fn parse_events(event: String) -> Result { + let _unhex = Vec::from_hex(event).map_err(|_| "Decoding Events Failed".to_string())?; + let mut _er_enc = _unhex.as_slice(); + Events::decode(&mut _er_enc).map_err(|_| "Decoding Events Failed".to_string()) +} + +fn print_events(events: Events, _sender: Sender) { + for evr in &events { + debug!("Decoded: phase = {:?}, event = {:?}", evr.phase, evr.event); + match &evr.event { + Event::Balances(be) => { + info!("[+] Received balances event"); + debug!("{:?}", be); + match &be { + pallet_balances::Event::Transfer { + from: transactor, + to: dest, + amount: value, + } => { + debug!(" Transactor: {:?}", transactor.to_ss58check()); + debug!(" Destination: {:?}", dest.to_ss58check()); + debug!(" Value: {:?}", value); + }, + _ => { + trace!("Ignoring unsupported balances event"); + }, + } + }, + Event::Teerex(re) => { + debug!("{:?}", re); + match &re { + my_node_runtime::pallet_teerex::Event::AddedEnclave(sender, worker_url) => { + println!("[+] Received AddedEnclave event"); + println!(" Sender (Worker): {:?}", sender); + println!(" Registered URL: {:?}", str::from_utf8(worker_url).unwrap()); + }, + my_node_runtime::pallet_teerex::Event::Forwarded(shard) => { + println!( + "[+] Received trusted call for shard {}", + shard.encode().to_base58() + ); + }, + my_node_runtime::pallet_teerex::Event::ProcessedParentchainBlock( + sender, + block_hash, + merkle_root, + block_number, + ) => { + info!("[+] Received ProcessedParentchainBlock event"); + debug!(" From: {:?}", sender); + debug!(" Block Hash: {:?}", hex::encode(block_hash)); + debug!(" Merkle Root: {:?}", hex::encode(merkle_root)); + debug!(" Block Number: {:?}", block_number); + }, + my_node_runtime::pallet_teerex::Event::ShieldFunds(incognito_account) => { + info!("[+] Received ShieldFunds event"); + debug!(" For: {:?}", incognito_account); + }, + my_node_runtime::pallet_teerex::Event::UnshieldedFunds(incognito_account) => { + info!("[+] Received UnshieldedFunds event"); + debug!(" For: {:?}", incognito_account); + }, + _ => { + trace!("Ignoring unsupported pallet_teerex event"); + }, + } + }, + #[cfg(feature = "teeracle")] + Event::Teeracle(re) => { + debug!("{:?}", re); + match &re { + my_node_runtime::pallet_teeracle::Event::ExchangeRateUpdated( + source, + currency, + new_value, + ) => { + println!("[+] Received ExchangeRateUpdated event"); + println!(" Data source: {:?}", source); + println!(" Currency: {:?}", currency); + println!(" Exchange rate: {:?}", new_value); + }, + my_node_runtime::pallet_teeracle::Event::ExchangeRateDeleted( + source, + currency, + ) => { + println!("[+] Received ExchangeRateDeleted event"); + println!(" Data source: {:?}", source); + println!(" Currency: {:?}", currency); + }, + my_node_runtime::pallet_teeracle::Event::AddedToWhitelist( + source, + mrenclave, + ) => { + println!("[+] Received AddedToWhitelist event"); + println!(" Data source: {:?}", source); + println!(" Currency: {:?}", mrenclave); + }, + my_node_runtime::pallet_teeracle::Event::RemovedFromWhitelist( + source, + mrenclave, + ) => { + println!("[+] Received RemovedFromWhitelist event"); + println!(" Data source: {:?}", source); + println!(" Currency: {:?}", mrenclave); + }, + _ => { + trace!("Ignoring unsupported pallet_teeracle event"); + }, + } + }, + #[cfg(feature = "sidechain")] + Event::Sidechain(re) => match &re { + my_node_runtime::pallet_sidechain::Event::ProposedSidechainBlock( + sender, + payload, + ) => { + info!("[+] Received ProposedSidechainBlock event"); + debug!(" From: {:?}", sender); + debug!(" Payload: {:?}", hex::encode(payload)); + }, + _ => { + trace!("Ignoring unsupported pallet_sidechain event"); + }, + }, + _ => { + trace!("Ignoring event {:?}", evr); + }, + } + } +} + +/// Subscribe to the node API finalized heads stream and trigger a parent chain sync +/// upon receiving a new header. +fn subscribe_to_parentchain_new_headers( + parentchain_handler: Arc>, + mut last_synced_header: Header, +) -> Result<(), Error> { + let (sender, receiver) = channel(); + //TODO: this should be implemented by parentchain_handler directly, and not via + // exposed parentchain_api. Blocked by https://github.com/scs/substrate-api-client/issues/267. + parentchain_handler + .parentchain_api() + .subscribe_finalized_heads(sender) + .map_err(Error::ApiClient)?; + + loop { + let new_header: Header = match receiver.recv() { + Ok(header_str) => serde_json::from_str(&header_str).map_err(Error::Serialization), + Err(e) => Err(Error::ApiSubscriptionDisconnected(e)), + }?; + + println!( + "[+] Received finalized header update ({}), syncing parent chain...", + new_header.number + ); + + last_synced_header = parentchain_handler.sync_parentchain(last_synced_header)?; + } +} + +/// Get the public signing key of the TEE. +fn enclave_account(enclave_api: &E) -> AccountId32 { + let tee_public = enclave_api.get_ecc_signing_pubkey().unwrap(); + trace!("[+] Got ed25519 account of TEE = {}", tee_public.to_ss58check()); + AccountId32::from(*tee_public.as_array_ref()) +} + +/// Checks if we are the first validateer to register on the parentchain. +fn we_are_primary_validateer( + node_api: &ParentchainApi, + register_enclave_xt_header: &Header, +) -> Result { + let enclave_count_of_previous_block = + node_api.enclave_count(Some(*register_enclave_xt_header.parent_hash()))?; + Ok(enclave_count_of_previous_block == 0) +} diff --git a/tee-worker/service/src/ocall_bridge/bridge_api.rs b/tee-worker/service/src/ocall_bridge/bridge_api.rs new file mode 100644 index 0000000000..2d098ae2cd --- /dev/null +++ b/tee-worker/service/src/ocall_bridge/bridge_api.rs @@ -0,0 +1,236 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + Copyright (C) 2017-2019 Baidu, Inc. All Rights Reserved. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +use lazy_static::lazy_static; +use log::*; +use parking_lot::RwLock; +use sgx_types::{ + sgx_epid_group_id_t, sgx_platform_info_t, sgx_quote_nonce_t, sgx_quote_sign_type_t, + sgx_report_t, sgx_spid_t, sgx_status_t, sgx_target_info_t, sgx_update_info_bit_t, +}; +use std::{sync::Arc, vec::Vec}; + +#[cfg(test)] +use mockall::predicate::*; +#[cfg(test)] +use mockall::*; + +lazy_static! { + /// global state for the component factory + /// access is always routed through 'Bridge', do not use directly! + static ref COMPONENT_FACTORY: RwLock>> = + RwLock::new(None); +} + +/// The Bridge is the static/global interface to inject concrete implementations +/// (or rather the factories for them) - this is done at startup of the worker. +/// On the other side, it is used by the o-call FFI to retrieve the state and forward calls +/// to their respective implementation. +pub struct Bridge; + +impl Bridge { + pub fn get_ra_api() -> Arc { + debug!("Requesting RemoteAttestation OCall API instance"); + + COMPONENT_FACTORY + .read() + .as_ref() + .expect("Component factory has not been set. Use `initialize()`") + .get_ra_api() + } + + pub fn get_sidechain_api() -> Arc { + COMPONENT_FACTORY + .read() + .as_ref() + .expect("Component factory has not been set. Use `initialize()`") + .get_sidechain_api() + } + + pub fn get_oc_api() -> Arc { + debug!("Requesting WorkerOnChain OCall API instance"); + + COMPONENT_FACTORY + .read() + .as_ref() + .expect("Component factory has not been set. Use `initialize()`") + .get_oc_api() + } + + pub fn get_ipfs_api() -> Arc { + debug!("Requesting IPFS OCall API instance"); + + COMPONENT_FACTORY + .read() + .as_ref() + .expect("Component factory has not been set. Use `initialize()`") + .get_ipfs_api() + } + + pub fn get_metrics_api() -> Arc { + COMPONENT_FACTORY + .read() + .as_ref() + .expect("Component factory has not been set. Use `initialize()`") + .get_metrics_api() + } + + pub fn initialize(component_factory: Arc) { + debug!("Initializing OCall bridge with component factory"); + + *COMPONENT_FACTORY.write() = Some(component_factory); + } +} + +/// Factory trait (abstract factory) that creates instances +/// of all the components of the OCall Bridge +pub trait GetOCallBridgeComponents { + /// remote attestation OCall API + fn get_ra_api(&self) -> Arc; + + /// side chain OCall API + fn get_sidechain_api(&self) -> Arc; + + /// on chain (parentchain) OCall API + fn get_oc_api(&self) -> Arc; + + /// ipfs OCall API + fn get_ipfs_api(&self) -> Arc; + + /// Metrics OCall API. + fn get_metrics_api(&self) -> Arc; +} + +/// OCall bridge errors +#[derive(Debug, thiserror::Error)] +pub enum OCallBridgeError { + #[error("GetQuote Error: {0}")] + GetQuote(sgx_status_t), + #[error("InitQuote Error: {0}")] + InitQuote(sgx_status_t), + #[error("GetUpdateInfo Error: {0}")] + GetUpdateInfo(sgx_status_t), + #[error("GetIasSocket Error: {0}")] + GetIasSocket(String), + #[error("UpdateMetric Error: {0}")] + UpdateMetric(String), + #[error("Propose sidechain block failed: {0}")] + ProposeSidechainBlock(String), + #[error("Failed to fetch sidechain blocks from peer: {0}")] + FetchSidechainBlocksFromPeer(String), + #[error("Sending extrinsics to parentchain failed: {0}")] + SendExtrinsicsToParentchain(String), + #[error("IPFS Error: {0}")] + IpfsError(String), + #[error("DirectInvocation Error: {0}")] + DirectInvocationError(String), + #[error("Node API factory error: {0}")] + NodeApiFactory(#[from] itp_node_api::node_api_factory::NodeApiFactoryError), +} + +impl From for sgx_status_t { + fn from(o: OCallBridgeError) -> sgx_status_t { + match o { + OCallBridgeError::GetQuote(s) => s, + OCallBridgeError::InitQuote(s) => s, + OCallBridgeError::GetUpdateInfo(s) => s, + _ => sgx_status_t::SGX_ERROR_UNEXPECTED, + } + } +} + +pub type OCallBridgeResult = Result; + +/// Trait for all the OCalls related to remote attestation +#[cfg_attr(test, automock)] +pub trait RemoteAttestationBridge { + /// initialize the quote + fn init_quote(&self) -> OCallBridgeResult<(sgx_target_info_t, sgx_epid_group_id_t)>; + + /// get the intel attestation service socket + fn get_ias_socket(&self) -> OCallBridgeResult; + + /// retrieve the quote from intel + fn get_quote( + &self, + revocation_list: Vec, + report: sgx_report_t, + quote_type: sgx_quote_sign_type_t, + spid: sgx_spid_t, + quote_nonce: sgx_quote_nonce_t, + ) -> OCallBridgeResult<(sgx_report_t, Vec)>; + + /// -- + fn get_update_info( + &self, + platform_blob: sgx_platform_info_t, + enclave_trusted: i32, + ) -> OCallBridgeResult; +} + +/// Trait for all the OCalls related to parentchain operations +#[cfg_attr(test, automock)] +pub trait WorkerOnChainBridge { + fn worker_request(&self, request: Vec) -> OCallBridgeResult>; + + fn send_to_parentchain(&self, extrinsics_encoded: Vec) -> OCallBridgeResult<()>; +} + +/// Trait for updating metrics from inside the enclave. +#[cfg_attr(test, automock)] +pub trait MetricsBridge { + fn update_metric(&self, metric_encoded: Vec) -> OCallBridgeResult<()>; +} + +/// Trait for all the OCalls related to sidechain operations +#[cfg_attr(test, automock)] +pub trait SidechainBridge { + fn propose_sidechain_blocks(&self, signed_blocks_encoded: Vec) -> OCallBridgeResult<()>; + + fn store_sidechain_blocks(&self, signed_blocks_encoded: Vec) -> OCallBridgeResult<()>; + + fn fetch_sidechain_blocks_from_peer( + &self, + last_imported_block_hash_encoded: Vec, + maybe_until_block_hash_encoded: Vec, + shard_identifier_encoded: Vec, + ) -> OCallBridgeResult>; +} + +/// type for IPFS +pub type Cid = [u8; 46]; + +/// Trait for all the OCalls related to IPFS +#[cfg_attr(test, automock)] +pub trait IpfsBridge { + fn write_to_ipfs(&self, data: &'static [u8]) -> OCallBridgeResult; + + fn read_from_ipfs(&self, cid: Cid) -> OCallBridgeResult<()>; +} + +/// Trait for the direct invocation OCalls +#[cfg_attr(test, automock)] +pub trait DirectInvocationBridge { + fn update_status_event( + &self, + hash_vec: Vec, + status_update_vec: Vec, + ) -> OCallBridgeResult<()>; + + fn send_status(&self, hash_vec: Vec, status_vec: Vec) -> OCallBridgeResult<()>; +} diff --git a/tee-worker/service/src/ocall_bridge/component_factory.rs b/tee-worker/service/src/ocall_bridge/component_factory.rs new file mode 100644 index 0000000000..b15e3116bf --- /dev/null +++ b/tee-worker/service/src/ocall_bridge/component_factory.rs @@ -0,0 +1,165 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + Copyright (C) 2017-2019 Baidu, Inc. All Rights Reserved. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +use crate::{ + ocall_bridge::{ + bridge_api::{ + GetOCallBridgeComponents, IpfsBridge, MetricsBridge, RemoteAttestationBridge, + SidechainBridge, WorkerOnChainBridge, + }, + ipfs_ocall::IpfsOCall, + metrics_ocall::MetricsOCall, + remote_attestation_ocall::RemoteAttestationOCall, + sidechain_ocall::SidechainOCall, + worker_on_chain_ocall::WorkerOnChainOCall, + }, + prometheus_metrics::ReceiveEnclaveMetrics, + sync_block_broadcaster::BroadcastBlocks, + worker_peers_updater::UpdateWorkerPeers, + GetTokioHandle, +}; +use itp_enclave_api::remote_attestation::RemoteAttestationCallBacks; +use itp_node_api::node_api_factory::CreateNodeApi; +use its_peer_fetch::FetchBlocksFromPeer; +use its_primitives::types::block::SignedBlock as SignedSidechainBlock; +use its_storage::BlockStorage; +use std::sync::Arc; + +/// Concrete implementation, should be moved out of the OCall Bridge, into the worker +/// since the OCall bridge itself should not know any concrete types to ensure +/// our dependency graph is worker -> ocall bridge +pub struct OCallBridgeComponentFactory< + NodeApi, + Broadcaster, + EnclaveApi, + Storage, + PeerUpdater, + PeerBlockFetcher, + TokioHandle, + MetricsReceiver, +> { + node_api_factory: Arc, + block_broadcaster: Arc, + enclave_api: Arc, + block_storage: Arc, + peer_updater: Arc, + peer_block_fetcher: Arc, + tokio_handle: Arc, + metrics_receiver: Arc, +} + +impl< + NodeApi, + Broadcaster, + EnclaveApi, + Storage, + PeerUpdater, + PeerBlockFetcher, + TokioHandle, + MetricsReceiver, + > + OCallBridgeComponentFactory< + NodeApi, + Broadcaster, + EnclaveApi, + Storage, + PeerUpdater, + PeerBlockFetcher, + TokioHandle, + MetricsReceiver, + > +{ + #[allow(clippy::too_many_arguments)] + pub fn new( + node_api_factory: Arc, + block_broadcaster: Arc, + enclave_api: Arc, + block_storage: Arc, + peer_updater: Arc, + peer_block_fetcher: Arc, + tokio_handle: Arc, + metrics_receiver: Arc, + ) -> Self { + OCallBridgeComponentFactory { + node_api_factory, + block_broadcaster, + enclave_api, + block_storage, + peer_updater, + peer_block_fetcher, + tokio_handle, + metrics_receiver, + } + } +} + +impl< + NodeApi, + Broadcaster, + EnclaveApi, + Storage, + PeerUpdater, + PeerBlockFetcher, + TokioHandle, + MetricsReceiver, + > GetOCallBridgeComponents + for OCallBridgeComponentFactory< + NodeApi, + Broadcaster, + EnclaveApi, + Storage, + PeerUpdater, + PeerBlockFetcher, + TokioHandle, + MetricsReceiver, + > where + NodeApi: CreateNodeApi + 'static, + Broadcaster: BroadcastBlocks + 'static, + EnclaveApi: RemoteAttestationCallBacks + 'static, + Storage: BlockStorage + 'static, + PeerUpdater: UpdateWorkerPeers + 'static, + PeerBlockFetcher: FetchBlocksFromPeer + 'static, + TokioHandle: GetTokioHandle + 'static, + MetricsReceiver: ReceiveEnclaveMetrics + 'static, +{ + fn get_ra_api(&self) -> Arc { + Arc::new(RemoteAttestationOCall::new(self.enclave_api.clone())) + } + + fn get_sidechain_api(&self) -> Arc { + Arc::new(SidechainOCall::new( + self.block_broadcaster.clone(), + self.block_storage.clone(), + self.peer_updater.clone(), + self.peer_block_fetcher.clone(), + self.tokio_handle.clone(), + )) + } + + fn get_oc_api(&self) -> Arc { + Arc::new(WorkerOnChainOCall::new(self.node_api_factory.clone())) + } + + fn get_ipfs_api(&self) -> Arc { + Arc::new(IpfsOCall {}) + } + + fn get_metrics_api(&self) -> Arc { + Arc::new(MetricsOCall::new(self.metrics_receiver.clone())) + } +} diff --git a/tee-worker/service/src/ocall_bridge/ffi/fetch_sidechain_blocks_from_peer.rs b/tee-worker/service/src/ocall_bridge/ffi/fetch_sidechain_blocks_from_peer.rs new file mode 100644 index 0000000000..f1433e1329 --- /dev/null +++ b/tee-worker/service/src/ocall_bridge/ffi/fetch_sidechain_blocks_from_peer.rs @@ -0,0 +1,193 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + Copyright (C) 2017-2019 Baidu, Inc. All Rights Reserved. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +use crate::ocall_bridge::bridge_api::{Bridge, SidechainBridge}; +use itp_utils::write_slice_and_whitespace_pad; +use log::*; +use sgx_types::sgx_status_t; +use std::{slice, sync::Arc}; + +/// # Safety +/// +/// FFI are always unsafe +#[no_mangle] +pub unsafe extern "C" fn ocall_fetch_sidechain_blocks_from_peer( + last_imported_block_hash_ptr: *const u8, + last_imported_block_hash_size: u32, + maybe_until_block_hash_ptr: *const u8, + maybe_until_block_hash_size: u32, + shard_identifier_ptr: *const u8, + shard_identifier_size: u32, + sidechain_blocks_ptr: *mut u8, + sidechain_blocks_size: u32, +) -> sgx_status_t { + fetch_sidechain_blocks_from_peer( + last_imported_block_hash_ptr, + last_imported_block_hash_size, + maybe_until_block_hash_ptr, + maybe_until_block_hash_size, + shard_identifier_ptr, + shard_identifier_size, + sidechain_blocks_ptr, + sidechain_blocks_size, + Bridge::get_sidechain_api(), + ) +} + +#[allow(clippy::too_many_arguments)] +fn fetch_sidechain_blocks_from_peer( + last_imported_block_hash_ptr: *const u8, + last_imported_block_hash_size: u32, + maybe_until_block_hash_ptr: *const u8, + maybe_until_block_hash_size: u32, + shard_identifier_ptr: *const u8, + shard_identifier_size: u32, + sidechain_blocks_ptr: *mut u8, + sidechain_blocks_size: u32, + sidechain_api: Arc, +) -> sgx_status_t { + let last_imported_block_hash_encoded = unsafe { + Vec::from(slice::from_raw_parts( + last_imported_block_hash_ptr, + last_imported_block_hash_size as usize, + )) + }; + let maybe_until_block_hash = unsafe { + Vec::from(slice::from_raw_parts( + maybe_until_block_hash_ptr, + maybe_until_block_hash_size as usize, + )) + }; + let shard_identifier_encoded = unsafe { + Vec::from(slice::from_raw_parts(shard_identifier_ptr, shard_identifier_size as usize)) + }; + + let sidechain_blocks_encoded = match sidechain_api.fetch_sidechain_blocks_from_peer( + last_imported_block_hash_encoded, + maybe_until_block_hash, + shard_identifier_encoded, + ) { + Ok(r) => r, + Err(e) => { + error!("fetch sidechain blocks from peer failed: {:?}", e); + return sgx_status_t::SGX_ERROR_UNEXPECTED + }, + }; + + let sidechain_blocks_encoded_slice = + unsafe { slice::from_raw_parts_mut(sidechain_blocks_ptr, sidechain_blocks_size as usize) }; + if let Err(e) = + write_slice_and_whitespace_pad(sidechain_blocks_encoded_slice, sidechain_blocks_encoded) + { + error!("Failed to transfer encoded sidechain blocks to o-call buffer: {:?}", e); + return sgx_status_t::SGX_ERROR_UNEXPECTED + } + + sgx_status_t::SGX_SUCCESS +} + +#[cfg(test)] +mod tests { + + use super::*; + use crate::ocall_bridge::test::mocks::sidechain_bridge_mock::SidechainBridgeMock; + use codec::{Decode, Encode}; + use its_primitives::types::block::SignedBlock; + use its_test::sidechain_block_builder::SidechainBlockBuilder; + use primitive_types::H256; + + #[test] + fn fetch_sidechain_blocks_from_peer_works() { + let sidechain_blocks = vec![ + SidechainBlockBuilder::random().build_signed(), + SidechainBlockBuilder::random().build_signed(), + ]; + + let sidechain_bridge_mock = + Arc::new(SidechainBridgeMock::default().with_peer_blocks(sidechain_blocks.encode())); + + let last_known_block_hash = H256::random(); + let shard_identifier = H256::random(); + let mut block_buffer = vec![0; 16 * 4096]; + + let result = call_fetch_sidechain_blocks_from_peer( + last_known_block_hash, + None, + shard_identifier, + &mut block_buffer, + sidechain_bridge_mock, + ); + + let decoded_blocks: Vec = + Decode::decode(&mut block_buffer.as_slice()).unwrap(); + + assert_eq!(result, sgx_status_t::SGX_SUCCESS); + assert_eq!(sidechain_blocks, decoded_blocks); + } + + #[test] + fn returns_error_if_buffer_is_too_small() { + let sidechain_blocks = vec![ + SidechainBlockBuilder::random().build_signed(), + SidechainBlockBuilder::random().build_signed(), + SidechainBlockBuilder::random().build_signed(), + SidechainBlockBuilder::random().build_signed(), + ]; + + let sidechain_bridge_mock = + Arc::new(SidechainBridgeMock::default().with_peer_blocks(sidechain_blocks.encode())); + + let last_known_block_hash = H256::random(); + let shard_identifier = H256::random(); + let mut block_buffer = vec![0; 16]; // way too small to hold the encoded blocks + + let result = call_fetch_sidechain_blocks_from_peer( + last_known_block_hash, + None, + shard_identifier, + &mut block_buffer, + sidechain_bridge_mock, + ); + + assert_eq!(result, sgx_status_t::SGX_ERROR_UNEXPECTED); + } + + fn call_fetch_sidechain_blocks_from_peer( + last_imported_block_hash: H256, + maybe_until_block_hash: Option, + shard_identifier: H256, + buffer: &mut Vec, + sidechain_bridge: Arc, + ) -> sgx_status_t { + let last_imported_block_hash_encoded = last_imported_block_hash.encode(); + let maybe_until_block_hash_encoded = maybe_until_block_hash.encode(); + let shard_identifier_encoded = shard_identifier.encode(); + + fetch_sidechain_blocks_from_peer( + last_imported_block_hash_encoded.as_ptr(), + last_imported_block_hash_encoded.len() as u32, + maybe_until_block_hash_encoded.as_ptr(), + maybe_until_block_hash_encoded.len() as u32, + shard_identifier_encoded.as_ptr(), + shard_identifier_encoded.len() as u32, + buffer.as_mut_ptr(), + buffer.len() as u32, + sidechain_bridge, + ) + } +} diff --git a/tee-worker/service/src/ocall_bridge/ffi/get_ias_socket.rs b/tee-worker/service/src/ocall_bridge/ffi/get_ias_socket.rs new file mode 100644 index 0000000000..4b48d2b1ad --- /dev/null +++ b/tee-worker/service/src/ocall_bridge/ffi/get_ias_socket.rs @@ -0,0 +1,86 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + Copyright (C) 2017-2019 Baidu, Inc. All Rights Reserved. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +use crate::ocall_bridge::bridge_api::{Bridge, RemoteAttestationBridge}; +use log::*; +use sgx_types::{c_int, sgx_status_t}; +use std::sync::Arc; + +#[no_mangle] +pub extern "C" fn ocall_get_ias_socket(ret_fd: *mut c_int) -> sgx_status_t { + get_ias_socket(ret_fd, Bridge::get_ra_api()) // inject the RA API (global state) +} + +fn get_ias_socket(ret_fd: *mut c_int, ra_api: Arc) -> sgx_status_t { + debug!(" Entering ocall_get_ias_socket"); + let socket_result = ra_api.get_ias_socket(); + + return match socket_result { + Ok(s) => { + unsafe { + *ret_fd = s; + } + sgx_status_t::SGX_SUCCESS + }, + Err(e) => { + error!("[-] Failed to get IAS socket: {:?}", e); + return e.into() + }, + } +} + +#[cfg(test)] +mod tests { + + use super::*; + use crate::ocall_bridge::bridge_api::{MockRemoteAttestationBridge, OCallBridgeError}; + use std::sync::Arc; + + #[test] + fn get_socket_sets_pointer_result() { + let expected_socket = 4321i32; + + let mut ra_ocall_api_mock = MockRemoteAttestationBridge::new(); + ra_ocall_api_mock + .expect_get_ias_socket() + .times(1) + .returning(move || Ok(expected_socket)); + + let mut ias_sock: i32 = 0; + + let ret_status = get_ias_socket(&mut ias_sock as *mut i32, Arc::new(ra_ocall_api_mock)); + + assert_eq!(ret_status, sgx_status_t::SGX_SUCCESS); + assert_eq!(ias_sock, expected_socket); + } + + #[test] + fn given_error_from_ocall_impl_then_return_sgx_error() { + let mut ra_ocall_api_mock = MockRemoteAttestationBridge::new(); + ra_ocall_api_mock + .expect_get_ias_socket() + .times(1) + .returning(|| Err(OCallBridgeError::GetIasSocket("test error".to_string()))); + + let mut ias_sock: i32 = 0; + let ret_status = get_ias_socket(&mut ias_sock as *mut i32, Arc::new(ra_ocall_api_mock)); + + assert_ne!(ret_status, sgx_status_t::SGX_SUCCESS); + assert_eq!(ias_sock, 0); + } +} diff --git a/tee-worker/service/src/ocall_bridge/ffi/get_quote.rs b/tee-worker/service/src/ocall_bridge/ffi/get_quote.rs new file mode 100644 index 0000000000..8123636707 --- /dev/null +++ b/tee-worker/service/src/ocall_bridge/ffi/get_quote.rs @@ -0,0 +1,99 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + Copyright (C) 2017-2019 Baidu, Inc. All Rights Reserved. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +use crate::ocall_bridge::bridge_api::{Bridge, RemoteAttestationBridge}; +use log::*; +use sgx_types::{sgx_quote_nonce_t, sgx_quote_sign_type_t, sgx_report_t, sgx_spid_t, sgx_status_t}; +use std::{slice, sync::Arc}; + +#[no_mangle] +pub extern "C" fn ocall_get_quote( + p_sigrl: *const u8, + sigrl_len: u32, + p_report: *const sgx_report_t, + quote_type: sgx_quote_sign_type_t, + p_spid: *const sgx_spid_t, + p_nonce: *const sgx_quote_nonce_t, + p_qe_report: *mut sgx_report_t, + p_quote: *mut u8, + maxlen: u32, + p_quote_len: *mut u32, +) -> sgx_status_t { + get_quote( + p_sigrl, + sigrl_len, + p_report, + quote_type, + p_spid, + p_nonce, + p_qe_report, + p_quote, + maxlen, + p_quote_len, + Bridge::get_ra_api(), // inject the RA API (global state) + ) +} + +#[allow(clippy::too_many_arguments)] +fn get_quote( + p_sigrl: *const u8, + sigrl_len: u32, + p_report: *const sgx_report_t, + quote_type: sgx_quote_sign_type_t, + p_spid: *const sgx_spid_t, + p_nonce: *const sgx_quote_nonce_t, + p_qe_report: *mut sgx_report_t, + p_quote: *mut u8, + maxlen: u32, + p_quote_len: *mut u32, + ra_api: Arc, +) -> sgx_status_t { + debug!(" Entering ocall_get_quote"); + + let revocation_list: Vec = + unsafe { slice::from_raw_parts(p_sigrl, sigrl_len as usize).to_vec() }; + + let report = unsafe { *p_report }; + let spid = unsafe { *p_spid }; + let quote_nonce = unsafe { *p_nonce }; + + let get_quote_result = + match ra_api.get_quote(revocation_list, report, quote_type, spid, quote_nonce) { + Ok(r) => r, + Err(e) => { + error!("[-] Failed to get quote: {:?}", e); + return e.into() + }, + }; + + let quote = get_quote_result.1; + + if quote.len() as u32 > maxlen { + return sgx_status_t::SGX_ERROR_FAAS_BUFFER_TOO_SHORT + } + + let quote_slice = unsafe { slice::from_raw_parts_mut(p_quote, quote.len()) }; + quote_slice.clone_from_slice(quote.as_slice()); + + unsafe { + *p_qe_report = get_quote_result.0; + *p_quote_len = quote.len() as u32; + }; + + sgx_status_t::SGX_SUCCESS +} diff --git a/tee-worker/service/src/ocall_bridge/ffi/get_update_info.rs b/tee-worker/service/src/ocall_bridge/ffi/get_update_info.rs new file mode 100644 index 0000000000..55a9c7bfb4 --- /dev/null +++ b/tee-worker/service/src/ocall_bridge/ffi/get_update_info.rs @@ -0,0 +1,61 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + Copyright (C) 2017-2019 Baidu, Inc. All Rights Reserved. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +use crate::ocall_bridge::bridge_api::{Bridge, RemoteAttestationBridge}; +use log::*; +use sgx_types::{sgx_platform_info_t, sgx_status_t, sgx_update_info_bit_t}; +use std::sync::Arc; + +#[no_mangle] +pub extern "C" fn ocall_get_update_info( + p_platform_blob: *const sgx_platform_info_t, + enclave_trusted: i32, + p_update_info: *mut sgx_update_info_bit_t, +) -> sgx_status_t { + get_update_info( + p_platform_blob, + enclave_trusted, + p_update_info, + Bridge::get_ra_api(), // inject the RA API (global state) + ) +} + +fn get_update_info( + p_platform_blob: *const sgx_platform_info_t, + enclave_trusted: i32, + p_update_info: *mut sgx_update_info_bit_t, + ra_api: Arc, +) -> sgx_status_t { + debug!(" Entering ocall_get_update_info"); + + let platform_blob = unsafe { *p_platform_blob }; + + let update_info_result = match ra_api.get_update_info(platform_blob, enclave_trusted) { + Ok(r) => r, + Err(e) => { + error!("[-] Failed to get update info: {:?}", e); + return e.into() + }, + }; + + unsafe { + *p_update_info = update_info_result; + } + + sgx_status_t::SGX_SUCCESS +} diff --git a/tee-worker/service/src/ocall_bridge/ffi/init_quote.rs b/tee-worker/service/src/ocall_bridge/ffi/init_quote.rs new file mode 100644 index 0000000000..7c181a0ad6 --- /dev/null +++ b/tee-worker/service/src/ocall_bridge/ffi/init_quote.rs @@ -0,0 +1,85 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + Copyright (C) 2017-2019 Baidu, Inc. All Rights Reserved. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +use crate::ocall_bridge::bridge_api::{Bridge, RemoteAttestationBridge}; +use log::*; +use sgx_types::{sgx_epid_group_id_t, sgx_status_t, sgx_target_info_t}; +use std::sync::Arc; + +#[no_mangle] +pub extern "C" fn ocall_sgx_init_quote( + ret_ti: *mut sgx_target_info_t, + ret_gid: *mut sgx_epid_group_id_t, +) -> sgx_status_t { + sgx_init_quote(ret_ti, ret_gid, Bridge::get_ra_api()) // inject the RA API (global state) +} + +fn sgx_init_quote( + ret_ti: *mut sgx_target_info_t, + ret_gid: *mut sgx_epid_group_id_t, + ra_api: Arc, +) -> sgx_status_t { + debug!(" Entering ocall_sgx_init_quote"); + let init_result = match ra_api.init_quote() { + Ok(r) => r, + Err(e) => { + error!("[-] Failed to init quote: {:?}", e); + return e.into() + }, + }; + + unsafe { + *ret_ti = init_result.0; + *ret_gid = init_result.1; + } + + sgx_status_t::SGX_SUCCESS +} + +#[cfg(test)] +mod tests { + + use super::*; + use crate::ocall_bridge::bridge_api::MockRemoteAttestationBridge; + use std::sync::Arc; + + #[test] + fn init_quote_sets_results() { + let mut ra_ocall_api_mock = MockRemoteAttestationBridge::new(); + ra_ocall_api_mock + .expect_init_quote() + .times(1) + .returning(|| Ok((dummy_target_info(), [8u8; 4]))); + + let mut ti: sgx_target_info_t = sgx_target_info_t::default(); + let mut eg: sgx_epid_group_id_t = sgx_epid_group_id_t::default(); + + let ret_status = sgx_init_quote( + &mut ti as *mut sgx_target_info_t, + &mut eg as *mut sgx_epid_group_id_t, + Arc::new(ra_ocall_api_mock), + ); + + assert_eq!(ret_status, sgx_status_t::SGX_SUCCESS); + assert_eq!(eg, [8u8; 4]); + } + + fn dummy_target_info() -> sgx_target_info_t { + sgx_target_info_t::default() + } +} diff --git a/tee-worker/service/src/ocall_bridge/ffi/ipfs.rs b/tee-worker/service/src/ocall_bridge/ffi/ipfs.rs new file mode 100644 index 0000000000..e264b49db2 --- /dev/null +++ b/tee-worker/service/src/ocall_bridge/ffi/ipfs.rs @@ -0,0 +1,76 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + Copyright (C) 2017-2019 Baidu, Inc. All Rights Reserved. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +use crate::ocall_bridge::bridge_api::{Bridge, Cid, IpfsBridge}; +use log::*; +use sgx_types::sgx_status_t; +use std::{slice, sync::Arc}; + +/// C-API exposed for o-call from enclave +#[no_mangle] +pub unsafe extern "C" fn ocall_write_ipfs( + enc_state: *const u8, + enc_state_size: u32, + cid: *mut u8, + cid_size: u32, +) -> sgx_status_t { + write_ipfs(enc_state, enc_state_size, cid, cid_size, Bridge::get_ipfs_api()) +} + +/// C-API exposed for o-call from enclave +#[no_mangle] +pub unsafe extern "C" fn ocall_read_ipfs(cid: *const u8, cid_size: u32) -> sgx_status_t { + read_ipfs(cid, cid_size, Bridge::get_ipfs_api()) +} + +fn write_ipfs( + enc_state: *const u8, + enc_state_size: u32, + cid: *mut u8, + cid_size: u32, + ipfs_api: Arc, +) -> sgx_status_t { + let state = unsafe { slice::from_raw_parts(enc_state, enc_state_size as usize) }; + let cid = unsafe { slice::from_raw_parts_mut(cid, cid_size as usize) }; + + return match ipfs_api.write_to_ipfs(state) { + Ok(r) => { + cid.clone_from_slice(&r); + sgx_status_t::SGX_SUCCESS + }, + Err(e) => { + error!("OCall to write_ipfs failed: {:?}", e); + sgx_status_t::SGX_ERROR_UNEXPECTED + }, + } +} + +fn read_ipfs(cid: *const u8, cid_size: u32, ipfs_api: Arc) -> sgx_status_t { + let _cid = unsafe { slice::from_raw_parts(cid, cid_size as usize) }; + + let mut cid: Cid = [0; 46]; + cid.clone_from_slice(_cid); + + match ipfs_api.read_from_ipfs(cid) { + Ok(_) => sgx_status_t::SGX_SUCCESS, + Err(e) => { + error!("OCall to read_ipfs failed: {:?}", e); + sgx_status_t::SGX_ERROR_UNEXPECTED + }, + } +} diff --git a/tee-worker/service/src/ocall_bridge/ffi/mod.rs b/tee-worker/service/src/ocall_bridge/ffi/mod.rs new file mode 100644 index 0000000000..32a549ac9f --- /dev/null +++ b/tee-worker/service/src/ocall_bridge/ffi/mod.rs @@ -0,0 +1,34 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + Copyright (C) 2017-2019 Baidu, Inc. All Rights Reserved. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +//! Foreign Function interface for all the OCalls. +//! Implementations of C-API functions, that can be called from the Enclave. +//! These should just be wrappers that transform the C-API structures and call the +//! actual implementation of the OCalls (using the traits defined in the bridge_api). + +pub mod fetch_sidechain_blocks_from_peer; +pub mod get_ias_socket; +pub mod get_quote; +pub mod get_update_info; +pub mod init_quote; +pub mod ipfs; +pub mod propose_sidechain_blocks; +pub mod send_to_parentchain; +pub mod store_sidechain_blocks; +pub mod update_metric; +pub mod worker_request; diff --git a/tee-worker/service/src/ocall_bridge/ffi/propose_sidechain_blocks.rs b/tee-worker/service/src/ocall_bridge/ffi/propose_sidechain_blocks.rs new file mode 100644 index 0000000000..21ff07d0bb --- /dev/null +++ b/tee-worker/service/src/ocall_bridge/ffi/propose_sidechain_blocks.rs @@ -0,0 +1,50 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + Copyright (C) 2017-2019 Baidu, Inc. All Rights Reserved. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +use crate::ocall_bridge::bridge_api::{Bridge, SidechainBridge}; +use log::*; +use sgx_types::sgx_status_t; +use std::{slice, sync::Arc}; + +/// # Safety +/// +/// FFI are always unsafe +#[no_mangle] +pub unsafe extern "C" fn ocall_propose_sidechain_blocks( + signed_blocks_ptr: *const u8, + signed_blocks_size: u32, +) -> sgx_status_t { + propose_sidechain_blocks(signed_blocks_ptr, signed_blocks_size, Bridge::get_sidechain_api()) +} + +fn propose_sidechain_blocks( + signed_blocks_ptr: *const u8, + signed_blocks_size: u32, + sidechain_api: Arc, +) -> sgx_status_t { + let signed_blocks_vec: Vec = + unsafe { Vec::from(slice::from_raw_parts(signed_blocks_ptr, signed_blocks_size as usize)) }; + + match sidechain_api.propose_sidechain_blocks(signed_blocks_vec) { + Ok(_) => sgx_status_t::SGX_SUCCESS, + Err(e) => { + error!("send sidechain blocks failed: {:?}", e); + sgx_status_t::SGX_ERROR_UNEXPECTED + }, + } +} diff --git a/tee-worker/service/src/ocall_bridge/ffi/send_to_parentchain.rs b/tee-worker/service/src/ocall_bridge/ffi/send_to_parentchain.rs new file mode 100644 index 0000000000..19bf289c24 --- /dev/null +++ b/tee-worker/service/src/ocall_bridge/ffi/send_to_parentchain.rs @@ -0,0 +1,51 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + Copyright (C) 2017-2019 Baidu, Inc. All Rights Reserved. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +use crate::ocall_bridge::bridge_api::{Bridge, WorkerOnChainBridge}; +use log::*; +use sgx_types::sgx_status_t; +use std::{slice, sync::Arc, vec::Vec}; + +/// # Safety +/// +/// FFI are always unsafe +#[no_mangle] +pub unsafe extern "C" fn ocall_send_to_parentchain( + extrinsics_encoded: *const u8, + extrinsics_encoded_size: u32, +) -> sgx_status_t { + send_to_parentchain(extrinsics_encoded, extrinsics_encoded_size, Bridge::get_oc_api()) +} + +fn send_to_parentchain( + extrinsics_encoded: *const u8, + extrinsics_encoded_size: u32, + oc_api: Arc, +) -> sgx_status_t { + let extrinsics_encoded_vec: Vec = unsafe { + Vec::from(slice::from_raw_parts(extrinsics_encoded, extrinsics_encoded_size as usize)) + }; + + match oc_api.send_to_parentchain(extrinsics_encoded_vec) { + Ok(_) => sgx_status_t::SGX_SUCCESS, + Err(e) => { + error!("send extrinsics_encoded failed: {:?}", e); + sgx_status_t::SGX_ERROR_UNEXPECTED + }, + } +} diff --git a/tee-worker/service/src/ocall_bridge/ffi/store_sidechain_blocks.rs b/tee-worker/service/src/ocall_bridge/ffi/store_sidechain_blocks.rs new file mode 100644 index 0000000000..70361d8fd7 --- /dev/null +++ b/tee-worker/service/src/ocall_bridge/ffi/store_sidechain_blocks.rs @@ -0,0 +1,50 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + Copyright (C) 2017-2019 Baidu, Inc. All Rights Reserved. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +use crate::ocall_bridge::bridge_api::{Bridge, SidechainBridge}; +use log::*; +use sgx_types::sgx_status_t; +use std::{slice, sync::Arc}; + +/// # Safety +/// +/// FFI are always unsafe +#[no_mangle] +pub unsafe extern "C" fn ocall_store_sidechain_blocks( + signed_blocks_ptr: *const u8, + signed_blocks_size: u32, +) -> sgx_status_t { + store_sidechain_blocks(signed_blocks_ptr, signed_blocks_size, Bridge::get_sidechain_api()) +} + +fn store_sidechain_blocks( + signed_blocks_ptr: *const u8, + signed_blocks_size: u32, + sidechain_api: Arc, +) -> sgx_status_t { + let signed_blocks_vec: Vec = + unsafe { Vec::from(slice::from_raw_parts(signed_blocks_ptr, signed_blocks_size as usize)) }; + + match sidechain_api.store_sidechain_blocks(signed_blocks_vec) { + Ok(_) => sgx_status_t::SGX_SUCCESS, + Err(e) => { + error!("store sidechain blocks failed: {:?}", e); + sgx_status_t::SGX_ERROR_UNEXPECTED + }, + } +} diff --git a/tee-worker/service/src/ocall_bridge/ffi/update_metric.rs b/tee-worker/service/src/ocall_bridge/ffi/update_metric.rs new file mode 100644 index 0000000000..0b97de74f9 --- /dev/null +++ b/tee-worker/service/src/ocall_bridge/ffi/update_metric.rs @@ -0,0 +1,50 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + Copyright (C) 2017-2019 Baidu, Inc. All Rights Reserved. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +use crate::ocall_bridge::bridge_api::{Bridge, MetricsBridge}; +use log::*; +use sgx_types::sgx_status_t; +use std::{slice, sync::Arc}; + +/// # Safety +/// +/// FFI are always unsafe +#[no_mangle] +pub unsafe extern "C" fn ocall_update_metric( + metric_ptr: *const u8, + metric_size: u32, +) -> sgx_status_t { + update_metric(metric_ptr, metric_size, Bridge::get_metrics_api()) +} + +fn update_metric( + metric_ptr: *const u8, + metric_size: u32, + oc_api: Arc, +) -> sgx_status_t { + let metric_encoded: Vec = + unsafe { Vec::from(slice::from_raw_parts(metric_ptr, metric_size as usize)) }; + + match oc_api.update_metric(metric_encoded) { + Ok(_) => sgx_status_t::SGX_SUCCESS, + Err(e) => { + error!("update_metric o-call failed: {:?}", e); + sgx_status_t::SGX_ERROR_UNEXPECTED + }, + } +} diff --git a/tee-worker/service/src/ocall_bridge/ffi/worker_request.rs b/tee-worker/service/src/ocall_bridge/ffi/worker_request.rs new file mode 100644 index 0000000000..64820175bf --- /dev/null +++ b/tee-worker/service/src/ocall_bridge/ffi/worker_request.rs @@ -0,0 +1,62 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + Copyright (C) 2017-2019 Baidu, Inc. All Rights Reserved. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +use crate::ocall_bridge::bridge_api::{Bridge, WorkerOnChainBridge}; +use itp_utils::write_slice_and_whitespace_pad; +use log::*; +use sgx_types::sgx_status_t; +use std::{slice, sync::Arc, vec::Vec}; + +/// # Safety +/// +/// FFI are always unsafe +#[no_mangle] +pub unsafe extern "C" fn ocall_worker_request( + request: *const u8, + req_size: u32, + response: *mut u8, + resp_size: u32, +) -> sgx_status_t { + worker_request(request, req_size, response, resp_size, Bridge::get_oc_api()) +} + +fn worker_request( + request: *const u8, + req_size: u32, + response: *mut u8, + resp_size: u32, + oc_api: Arc, +) -> sgx_status_t { + let request_vec: Vec = + unsafe { Vec::from(slice::from_raw_parts(request, req_size as usize)) }; + + match oc_api.worker_request(request_vec) { + Ok(r) => { + let resp_slice = unsafe { slice::from_raw_parts_mut(response, resp_size as usize) }; + if let Err(e) = write_slice_and_whitespace_pad(resp_slice, r) { + error!("Failed to transfer worker request response to o-call buffer: {:?}", e); + return sgx_status_t::SGX_ERROR_UNEXPECTED + } + sgx_status_t::SGX_SUCCESS + }, + Err(e) => { + error!("Worker request failed: {:?}", e); + sgx_status_t::SGX_ERROR_UNEXPECTED + }, + } +} diff --git a/tee-worker/service/src/ocall_bridge/ipfs_ocall.rs b/tee-worker/service/src/ocall_bridge/ipfs_ocall.rs new file mode 100644 index 0000000000..8cc25f85d7 --- /dev/null +++ b/tee-worker/service/src/ocall_bridge/ipfs_ocall.rs @@ -0,0 +1,102 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + Copyright (C) 2017-2019 Baidu, Inc. All Rights Reserved. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +use crate::ocall_bridge::bridge_api::{Cid, IpfsBridge, OCallBridgeError, OCallBridgeResult}; +use futures::TryStreamExt; +use ipfs_api::IpfsClient; +use log::*; +use std::{ + fs::File, + io::{Cursor, Write}, + str, + sync::mpsc::channel, +}; + +pub struct IpfsOCall; + +impl IpfsBridge for IpfsOCall { + fn write_to_ipfs(&self, data: &'static [u8]) -> OCallBridgeResult { + debug!(" Entering ocall_write_ipfs"); + Ok(write_to_ipfs(data)) + } + + fn read_from_ipfs(&self, cid: Cid) -> OCallBridgeResult<()> { + debug!("Entering ocall_read_ipfs"); + + let result = read_from_ipfs(cid); + match result { + Ok(res) => { + let filename = str::from_utf8(&cid).unwrap(); + create_file(filename, &res).map_err(OCallBridgeError::IpfsError) + }, + Err(_) => Err(OCallBridgeError::IpfsError("failed to read from IPFS".to_string())), + } + } +} + +fn create_file(filename: &str, result: &[u8]) -> Result<(), String> { + match File::create(filename) { + Ok(mut f) => f + .write_all(result) + .map_or_else(|e| Err(format!("failed writing to file: {}", e)), |_| Ok(())), + Err(e) => Err(format!("failed to create file: {}", e)), + } +} + +#[tokio::main] +async fn write_to_ipfs(data: &'static [u8]) -> Cid { + // Creates an `IpfsClient` connected to the endpoint specified in ~/.ipfs/api. + // If not found, tries to connect to `localhost:5001`. + let client = IpfsClient::default(); + + match client.version().await { + Ok(version) => info!("version: {:?}", version.version), + Err(e) => eprintln!("error getting version: {}", e), + } + + let datac = Cursor::new(data); + let (tx, rx) = channel(); + + match client.add(datac).await { + Ok(res) => { + info!("Result Hash {}", res.hash); + tx.send(res.hash.into_bytes()).unwrap(); + }, + Err(e) => eprintln!("error adding file: {}", e), + } + let mut cid: Cid = [0; 46]; + cid.clone_from_slice(&rx.recv().unwrap()); + cid +} + +#[tokio::main] +pub async fn read_from_ipfs(cid: Cid) -> Result, String> { + // Creates an `IpfsClient` connected to the endpoint specified in ~/.ipfs/api. + // If not found, tries to connect to `localhost:5001`. + let client = IpfsClient::default(); + let h = str::from_utf8(&cid).unwrap(); + + info!("Fetching content from: {}", h); + + client + .cat(h) + .map_ok(|chunk| chunk.to_vec()) + .map_err(|e| e.to_string()) + .try_concat() + .await +} diff --git a/tee-worker/service/src/ocall_bridge/metrics_ocall.rs b/tee-worker/service/src/ocall_bridge/metrics_ocall.rs new file mode 100644 index 0000000000..a06deff339 --- /dev/null +++ b/tee-worker/service/src/ocall_bridge/metrics_ocall.rs @@ -0,0 +1,51 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + Copyright (C) 2017-2019 Baidu, Inc. All Rights Reserved. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +use crate::{ + ocall_bridge::bridge_api::{MetricsBridge, OCallBridgeError, OCallBridgeResult}, + prometheus_metrics::ReceiveEnclaveMetrics, +}; +use codec::Decode; +use itp_enclave_metrics::EnclaveMetric; +use std::sync::Arc; + +pub struct MetricsOCall { + receiver: Arc, +} + +impl MetricsOCall { + pub fn new(receiver: Arc) -> Self { + MetricsOCall { receiver } + } +} + +impl MetricsBridge for MetricsOCall +where + MetricsReceiver: ReceiveEnclaveMetrics, +{ + fn update_metric(&self, metric_encoded: Vec) -> OCallBridgeResult<()> { + let metric: EnclaveMetric = + Decode::decode(&mut metric_encoded.as_slice()).map_err(|e| { + OCallBridgeError::UpdateMetric(format!("Failed to decode metric: {:?}", e)) + })?; + + self.receiver.receive_enclave_metric(metric).map_err(|e| { + OCallBridgeError::UpdateMetric(format!("Failed to receive enclave metric: {:?}", e)) + }) + } +} diff --git a/tee-worker/service/src/ocall_bridge/mod.rs b/tee-worker/service/src/ocall_bridge/mod.rs new file mode 100644 index 0000000000..91a5f8887f --- /dev/null +++ b/tee-worker/service/src/ocall_bridge/mod.rs @@ -0,0 +1,32 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + Copyright (C) 2017-2019 Baidu, Inc. All Rights Reserved. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +// TODO This entire module should be extracted to a separate crate and re-named to o-call tunnel, see #288 and #316 + +pub mod bridge_api; +pub mod component_factory; + +mod ffi; +mod ipfs_ocall; +mod metrics_ocall; +mod remote_attestation_ocall; +mod sidechain_ocall; +mod worker_on_chain_ocall; + +#[cfg(test)] +pub mod test; diff --git a/tee-worker/service/src/ocall_bridge/remote_attestation_ocall.rs b/tee-worker/service/src/ocall_bridge/remote_attestation_ocall.rs new file mode 100644 index 0000000000..268425c46a --- /dev/null +++ b/tee-worker/service/src/ocall_bridge/remote_attestation_ocall.rs @@ -0,0 +1,111 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + Copyright (C) 2017-2019 Baidu, Inc. All Rights Reserved. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +use crate::ocall_bridge::bridge_api::{ + OCallBridgeError, OCallBridgeResult, RemoteAttestationBridge, +}; +use itp_enclave_api::remote_attestation::RemoteAttestationCallBacks; +use sgx_types::*; +use std::{ + net::{SocketAddr, TcpStream}, + os::unix::io::IntoRawFd, + sync::Arc, +}; + +pub struct RemoteAttestationOCall { + enclave_api: Arc, +} + +impl RemoteAttestationOCall { + pub fn new(enclave_api: Arc) -> Self { + RemoteAttestationOCall { enclave_api } + } +} + +impl RemoteAttestationBridge for RemoteAttestationOCall +where + E: RemoteAttestationCallBacks, +{ + fn init_quote(&self) -> OCallBridgeResult<(sgx_target_info_t, sgx_epid_group_id_t)> { + self.enclave_api.init_quote().map_err(|e| match e { + itp_enclave_api::error::Error::Sgx(s) => OCallBridgeError::InitQuote(s), + _ => OCallBridgeError::InitQuote(sgx_status_t::SGX_ERROR_UNEXPECTED), + }) + } + + fn get_ias_socket(&self) -> OCallBridgeResult { + let port = 443; + let hostname = "api.trustedservices.intel.com"; + + let addr = lookup_ipv4(hostname, port).map_err(OCallBridgeError::GetIasSocket)?; + + let stream = TcpStream::connect(addr).map_err(|_| { + OCallBridgeError::GetIasSocket("[-] Connect tls server failed!".to_string()) + })?; + + Ok(stream.into_raw_fd()) + } + + fn get_quote( + &self, + revocation_list: Vec, + report: sgx_report_t, + quote_type: sgx_quote_sign_type_t, + spid: sgx_spid_t, + quote_nonce: sgx_quote_nonce_t, + ) -> OCallBridgeResult<(sgx_report_t, Vec)> { + let real_quote_len = + self.enclave_api.calc_quote_size(revocation_list.clone()).map_err(|e| match e { + itp_enclave_api::error::Error::Sgx(s) => OCallBridgeError::GetQuote(s), + _ => OCallBridgeError::GetQuote(sgx_status_t::SGX_ERROR_UNEXPECTED), + })?; + + self.enclave_api + .get_quote(revocation_list, report, quote_type, spid, quote_nonce, real_quote_len) + .map_err(|e| match e { + itp_enclave_api::error::Error::Sgx(s) => OCallBridgeError::GetQuote(s), + _ => OCallBridgeError::GetQuote(sgx_status_t::SGX_ERROR_UNEXPECTED), + }) + } + + fn get_update_info( + &self, + platform_blob: sgx_platform_info_t, + enclave_trusted: i32, + ) -> OCallBridgeResult { + self.enclave_api + .get_update_info(platform_blob, enclave_trusted) + .map_err(|e| match e { + itp_enclave_api::error::Error::Sgx(s) => OCallBridgeError::GetUpdateInfo(s), + _ => OCallBridgeError::GetUpdateInfo(sgx_status_t::SGX_ERROR_UNEXPECTED), + }) + } +} + +fn lookup_ipv4(host: &str, port: u16) -> Result { + use std::net::ToSocketAddrs; + + let addrs = (host, port).to_socket_addrs().map_err(|e| format!("{:?}", e))?; + for addr in addrs { + if let SocketAddr::V4(_) = addr { + return Ok(addr) + } + } + + Err("Cannot lookup address".to_string()) +} diff --git a/tee-worker/service/src/ocall_bridge/sidechain_ocall.rs b/tee-worker/service/src/ocall_bridge/sidechain_ocall.rs new file mode 100644 index 0000000000..fdc801fbe8 --- /dev/null +++ b/tee-worker/service/src/ocall_bridge/sidechain_ocall.rs @@ -0,0 +1,271 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + Copyright (C) 2017-2019 Baidu, Inc. All Rights Reserved. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +use crate::{ + ocall_bridge::bridge_api::{OCallBridgeError, OCallBridgeResult, SidechainBridge}, + sync_block_broadcaster::BroadcastBlocks, + worker_peers_updater::UpdateWorkerPeers, + GetTokioHandle, +}; +use codec::{Decode, Encode}; +use itp_types::{BlockHash, ShardIdentifier}; +use its_peer_fetch::FetchBlocksFromPeer; +use its_primitives::{traits::Block, types::SignedBlock as SignedSidechainBlock}; +use its_storage::BlockStorage; +use log::*; +use std::sync::Arc; + +pub struct SidechainOCall { + block_broadcaster: Arc, + block_storage: Arc, + peer_updater: Arc, + peer_block_fetcher: Arc, + tokio_handle: Arc, +} + +impl + SidechainOCall +{ + pub fn new( + block_broadcaster: Arc, + block_storage: Arc, + peer_updater: Arc, + peer_block_fetcher: Arc, + tokio_handle: Arc, + ) -> Self { + SidechainOCall { + block_broadcaster, + block_storage, + peer_updater, + peer_block_fetcher, + tokio_handle, + } + } +} + +impl SidechainBridge + for SidechainOCall +where + BlockBroadcaster: BroadcastBlocks, + Storage: BlockStorage, + PeerUpdater: UpdateWorkerPeers, + PeerBlockFetcher: FetchBlocksFromPeer, + TokioHandle: GetTokioHandle, +{ + fn propose_sidechain_blocks(&self, signed_blocks_encoded: Vec) -> OCallBridgeResult<()> { + // TODO: improve error handling, using a mut status is not good design? + let mut status: OCallBridgeResult<()> = Ok(()); + + // handle blocks + let signed_blocks: Vec = + match Decode::decode(&mut signed_blocks_encoded.as_slice()) { + Ok(blocks) => blocks, + Err(_) => { + status = Err(OCallBridgeError::ProposeSidechainBlock( + "Could not decode signed blocks".to_string(), + )); + vec![] + }, + }; + + if !signed_blocks.is_empty() { + info!( + "Enclave produced sidechain blocks: {:?}", + signed_blocks + .iter() + .map(|b| b.block.header().block_number) + .collect::>() + ); + } else { + debug!("Enclave did not produce sidechain blocks"); + } + + // FIXME: When & where should peers be updated? + debug!("Updating peers.."); + if let Err(e) = self.peer_updater.update_peers() { + error!("Error updating peers: {:?}", e); + // Fixme: returning an error here results in a `HeaderAncestryMismatch` error. + // status = sgx_status_t::SGX_ERROR_UNEXPECTED; + } else { + info!("Successfully updated peers"); + } + + debug!("Broadcasting sidechain blocks ..."); + if let Err(e) = self.block_broadcaster.broadcast_blocks(signed_blocks) { + error!("Error broadcasting blocks: {:?}", e); + // Fixme: returning an error here results in a `HeaderAncestryMismatch` error. + // status = sgx_status_t::SGX_ERROR_UNEXPECTED; + } else { + info!("Successfully broadcast blocks"); + } + + status + } + + fn store_sidechain_blocks(&self, signed_blocks_encoded: Vec) -> OCallBridgeResult<()> { + // TODO: improve error handling, using a mut status is not good design? + let mut status: OCallBridgeResult<()> = Ok(()); + + let signed_blocks: Vec = + match Decode::decode(&mut signed_blocks_encoded.as_slice()) { + Ok(blocks) => blocks, + Err(_) => { + status = Err(OCallBridgeError::ProposeSidechainBlock( + "Could not decode signed blocks".to_string(), + )); + vec![] + }, + }; + + if let Err(e) = self.block_storage.store_blocks(signed_blocks) { + error!("Error storing blocks: {:?}", e); + } + + status + } + + fn fetch_sidechain_blocks_from_peer( + &self, + last_imported_block_hash_encoded: Vec, + maybe_until_block_hash_encoded: Vec, + shard_identifier_encoded: Vec, + ) -> OCallBridgeResult> { + let last_imported_block_hash: BlockHash = + Decode::decode(&mut last_imported_block_hash_encoded.as_slice()).map_err(|_| { + OCallBridgeError::FetchSidechainBlocksFromPeer( + "Failed to decode last imported block hash".to_string(), + ) + })?; + + let maybe_until_block_hash: Option = + Decode::decode(&mut maybe_until_block_hash_encoded.as_slice()).map_err(|_| { + OCallBridgeError::FetchSidechainBlocksFromPeer( + "Failed to decode optional until block hash".to_string(), + ) + })?; + + let shard_identifier: ShardIdentifier = + Decode::decode(&mut shard_identifier_encoded.as_slice()).map_err(|_| { + OCallBridgeError::FetchSidechainBlocksFromPeer( + "Failed to decode shard identifier".to_string(), + ) + })?; + + info!("[O-call] fetching blocks from peer.."); + + let tokio_handle = self.tokio_handle.get_handle(); + + let signed_sidechain_blocks = tokio_handle + .block_on(self.peer_block_fetcher.fetch_blocks_from_peer( + last_imported_block_hash, + maybe_until_block_hash, + shard_identifier, + )) + .map_err(|e| { + OCallBridgeError::FetchSidechainBlocksFromPeer(format!( + "Failed to execute block fetching from peer: {:?}", + e + )) + })?; + + info!("[O-call] successfully fetched {} blocks from peer", signed_sidechain_blocks.len()); + + Ok(signed_sidechain_blocks.encode()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::{ + globals::tokio_handle::ScopedTokioHandle, + tests::mocks::{ + broadcast_blocks_mock::BroadcastBlocksMock, + update_worker_peers_mock::UpdateWorkerPeersMock, + }, + }; + use codec::Decode; + use its_peer_fetch::mocks::fetch_blocks_from_peer_mock::FetchBlocksFromPeerMock; + use its_primitives::types::block::SignedBlock as SignedSidechainBlock; + use its_storage::{interface::BlockStorage, Result as StorageResult}; + use its_test::sidechain_block_builder::SidechainBlockBuilder; + use primitive_types::H256; + use std::{collections::HashMap, vec::Vec}; + + struct BlockStorageMock; + impl BlockStorage for BlockStorageMock { + fn store_blocks(&self, _blocks: Vec) -> StorageResult<()> { + Ok(()) + } + } + + type TestSidechainOCall = SidechainOCall< + BroadcastBlocksMock, + BlockStorageMock, + UpdateWorkerPeersMock, + FetchBlocksFromPeerMock, + ScopedTokioHandle, + >; + + #[test] + fn fetch_sidechain_blocks_from_peer_works() { + let last_imported_block_hash = H256::random(); + let until_block_hash: Option = None; + let shard_identifier = H256::random(); + let blocks = vec![ + SidechainBlockBuilder::random().build_signed(), + SidechainBlockBuilder::random().build_signed(), + ]; + let peer_blocks_map = HashMap::from([(shard_identifier, blocks.clone())]); + let sidechain_ocall = setup_sidechain_ocall_with_peer_blocks(peer_blocks_map); + + let fetched_blocks_encoded = sidechain_ocall + .fetch_sidechain_blocks_from_peer( + last_imported_block_hash.encode(), + until_block_hash.encode(), + shard_identifier.encode(), + ) + .unwrap(); + + let fetched_blocks_decoded: Vec = + Decode::decode(&mut fetched_blocks_encoded.as_slice()).unwrap(); + + assert_eq!(blocks, fetched_blocks_decoded); + } + + fn setup_sidechain_ocall_with_peer_blocks( + peer_blocks_map: HashMap>, + ) -> TestSidechainOCall { + let block_broadcaster_mock = Arc::new(BroadcastBlocksMock {}); + let block_storage_mock = Arc::new(BlockStorageMock {}); + let peer_updater_mock = Arc::new(UpdateWorkerPeersMock {}); + let peer_block_fetcher_mock = Arc::new( + FetchBlocksFromPeerMock::::default() + .with_signed_blocks(peer_blocks_map), + ); + let scoped_tokio_handle = Arc::new(ScopedTokioHandle::default()); + + SidechainOCall::new( + block_broadcaster_mock, + block_storage_mock, + peer_updater_mock, + peer_block_fetcher_mock, + scoped_tokio_handle, + ) + } +} diff --git a/tee-worker/service/src/ocall_bridge/test/mocks/mod.rs b/tee-worker/service/src/ocall_bridge/test/mocks/mod.rs new file mode 100644 index 0000000000..298b05435a --- /dev/null +++ b/tee-worker/service/src/ocall_bridge/test/mocks/mod.rs @@ -0,0 +1,19 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + Copyright (C) 2017-2019 Baidu, Inc. All Rights Reserved. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +pub mod sidechain_bridge_mock; diff --git a/tee-worker/service/src/ocall_bridge/test/mocks/sidechain_bridge_mock.rs b/tee-worker/service/src/ocall_bridge/test/mocks/sidechain_bridge_mock.rs new file mode 100644 index 0000000000..65a81c2cf2 --- /dev/null +++ b/tee-worker/service/src/ocall_bridge/test/mocks/sidechain_bridge_mock.rs @@ -0,0 +1,50 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + Copyright (C) 2017-2019 Baidu, Inc. All Rights Reserved. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +use crate::ocall_bridge::bridge_api::{OCallBridgeResult, SidechainBridge}; + +#[derive(Default)] +pub struct SidechainBridgeMock { + peer_blocks_encoded: Vec, +} + +impl SidechainBridgeMock { + pub fn with_peer_blocks(mut self, blocks_encoded: Vec) -> Self { + self.peer_blocks_encoded = blocks_encoded; + self + } +} + +impl SidechainBridge for SidechainBridgeMock { + fn propose_sidechain_blocks(&self, _signed_blocks_encoded: Vec) -> OCallBridgeResult<()> { + Ok(()) + } + + fn store_sidechain_blocks(&self, _signed_blocks_encoded: Vec) -> OCallBridgeResult<()> { + Ok(()) + } + + fn fetch_sidechain_blocks_from_peer( + &self, + _last_imported_block_hash_encoded: Vec, + _maybe_until_block_hash_encoded: Vec, + _shard_identifier_encoded: Vec, + ) -> OCallBridgeResult> { + Ok(self.peer_blocks_encoded.clone()) + } +} diff --git a/tee-worker/service/src/ocall_bridge/test/mod.rs b/tee-worker/service/src/ocall_bridge/test/mod.rs new file mode 100644 index 0000000000..0c205a3799 --- /dev/null +++ b/tee-worker/service/src/ocall_bridge/test/mod.rs @@ -0,0 +1,19 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + Copyright (C) 2017-2019 Baidu, Inc. All Rights Reserved. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +pub mod mocks; diff --git a/tee-worker/service/src/ocall_bridge/worker_on_chain_ocall.rs b/tee-worker/service/src/ocall_bridge/worker_on_chain_ocall.rs new file mode 100644 index 0000000000..102e91cc38 --- /dev/null +++ b/tee-worker/service/src/ocall_bridge/worker_on_chain_ocall.rs @@ -0,0 +1,131 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + Copyright (C) 2017-2019 Baidu, Inc. All Rights Reserved. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +use crate::ocall_bridge::bridge_api::{OCallBridgeError, OCallBridgeResult, WorkerOnChainBridge}; +use codec::{Decode, Encode}; +use itp_node_api::node_api_factory::CreateNodeApi; +use itp_types::{WorkerRequest, WorkerResponse}; +use itp_utils::ToHexPrefixed; +use log::*; +use sp_core::storage::StorageKey; +use sp_runtime::OpaqueExtrinsic; +use std::{sync::Arc, vec::Vec}; +use substrate_api_client::XtStatus; + +pub struct WorkerOnChainOCall { + node_api_factory: Arc, +} + +impl WorkerOnChainOCall { + pub fn new(node_api_factory: Arc) -> Self { + WorkerOnChainOCall { node_api_factory } + } +} + +impl WorkerOnChainBridge for WorkerOnChainOCall +where + F: CreateNodeApi, +{ + fn worker_request(&self, request: Vec) -> OCallBridgeResult> { + debug!(" Entering ocall_worker_request"); + + let requests: Vec = Decode::decode(&mut request.as_slice()).unwrap(); + if requests.is_empty() { + debug!("requests is empty, returning empty vector"); + return Ok(Vec::::new().encode()) + } + + let api = self.node_api_factory.create_api()?; + + let resp: Vec>> = requests + .into_iter() + .map(|req| match req { + WorkerRequest::ChainStorage(key, hash) => WorkerResponse::ChainStorage( + key.clone(), + api.get_opaque_storage_by_key_hash(StorageKey(key.clone()), hash).unwrap(), + api.get_storage_proof_by_keys(vec![StorageKey(key)], hash).unwrap().map( + |read_proof| read_proof.proof.into_iter().map(|bytes| bytes.0).collect(), + ), + ), + }) + .collect(); + + let encoded_response: Vec = resp.encode(); + + Ok(encoded_response) + } + + fn send_to_parentchain(&self, extrinsics_encoded: Vec) -> OCallBridgeResult<()> { + // TODO: improve error handling, using a mut status is not good design? + let mut status: OCallBridgeResult<()> = Ok(()); + + let extrinsics: Vec = + match Decode::decode(&mut extrinsics_encoded.as_slice()) { + Ok(calls) => calls, + Err(_) => { + status = Err(OCallBridgeError::SendExtrinsicsToParentchain( + "Could not decode extrinsics".to_string(), + )); + Default::default() + }, + }; + + if !extrinsics.is_empty() { + debug!("Enclave wants to send {} extrinsics", extrinsics.len()); + let api = self.node_api_factory.create_api()?; + for call in extrinsics.into_iter() { + if let Err(e) = api.send_extrinsic(call.to_hex(), XtStatus::Ready) { + error!("Could not send extrsinic to node: {:?}", e); + } + } + } + + status + } +} + +#[cfg(test)] +mod tests { + + use super::*; + use itp_node_api::{ + api_client::ParentchainApi, + node_api_factory::{CreateNodeApi, Result as NodeApiResult}, + }; + use mockall::mock; + + #[test] + fn given_empty_worker_request_when_submitting_then_return_empty_response() { + mock! { + NodeApiFactory {} + impl CreateNodeApi for NodeApiFactory { + fn create_api(&self) -> NodeApiResult; + } + } + + let mock_node_api_factory = Arc::new(MockNodeApiFactory::new()); + + let on_chain_ocall = WorkerOnChainOCall::new(mock_node_api_factory); + + let response = on_chain_ocall.worker_request(Vec::::new().encode()).unwrap(); + + assert!(!response.is_empty()); // the encoded empty vector is not empty + let decoded_response: Vec = Decode::decode(&mut response.as_slice()).unwrap(); + assert!(decoded_response.is_empty()); // decode the response, and we get an empty vector again + } +} diff --git a/tee-worker/service/src/parentchain_handler.rs b/tee-worker/service/src/parentchain_handler.rs new file mode 100644 index 0000000000..2ddabea56b --- /dev/null +++ b/tee-worker/service/src/parentchain_handler.rs @@ -0,0 +1,176 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + Copyright (C) 2017-2019 Baidu, Inc. All Rights Reserved. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +use crate::error::{Error, ServiceResult}; +use itc_parentchain::{ + light_client::light_client_init_params::{GrandpaParams, SimpleParams}, + primitives::ParentchainInitParams, +}; +use itp_enclave_api::{enclave_base::EnclaveBase, sidechain::Sidechain}; +use itp_node_api::api_client::ChainApi; +use itp_types::SignedBlock; +use log::*; +use my_node_runtime::Header; +use sp_finality_grandpa::VersionedAuthorityList; +use sp_runtime::traits::Header as HeaderTrait; +use std::{cmp::min, sync::Arc}; + +const BLOCK_SYNC_BATCH_SIZE: u32 = 1000; + +pub trait HandleParentchain { + /// Initializes all parentchain specific components on the enclave side. + /// Returns the latest synced block header. + fn init_parentchain_components(&self) -> ServiceResult
; + + /// Fetches the parentchain blocks to sync from the parentchain and feeds them to the enclave. + /// Returns the latest synced block header. + fn sync_parentchain(&self, last_synced_header: Header) -> ServiceResult
; + + /// Triggers the import of the synced parentchain blocks inside the enclave. + fn trigger_parentchain_block_import(&self) -> ServiceResult<()>; + + /// Syncs and directly imports parentchain blocks from the latest synced header + /// until the specified until_header. + fn sync_and_import_parentchain_until( + &self, + last_synced_header: &Header, + until_header: &Header, + ) -> ServiceResult
; +} + +/// Handles the interaction between parentchain and enclave. +pub(crate) struct ParentchainHandler { + parentchain_api: ParentchainApi, + enclave_api: Arc, + parentchain_init_params: ParentchainInitParams, +} + +impl ParentchainHandler +where + ParentchainApi: ChainApi, + EnclaveApi: Sidechain + EnclaveBase, +{ + pub fn new( + parentchain_api: ParentchainApi, + enclave_api: Arc, + parentchain_init_params: ParentchainInitParams, + ) -> Self { + Self { parentchain_api, enclave_api, parentchain_init_params } + } + + // FIXME: Necessary in the future? Fix with #1080 + pub fn new_with_automatic_light_client_allocation( + parentchain_api: ParentchainApi, + enclave_api: Arc, + ) -> ServiceResult { + let genesis_hash = parentchain_api.get_genesis_hash()?; + let genesis_header: Header = parentchain_api + .get_header(Some(genesis_hash))? + .ok_or(Error::MissingGenesisHeader)?; + + let parentchain_init_params: ParentchainInitParams = if parentchain_api + .is_grandpa_available()? + { + let grandpas = parentchain_api.grandpa_authorities(Some(genesis_hash))?; + let grandpa_proof = parentchain_api.grandpa_authorities_proof(Some(genesis_hash))?; + + debug!("Grandpa Authority List: \n {:?} \n ", grandpas); + + let authority_list = VersionedAuthorityList::from(grandpas); + + GrandpaParams { + genesis_header, + authorities: authority_list.into(), + authority_proof: grandpa_proof, + } + .into() + } else { + SimpleParams { genesis_header }.into() + }; + + Ok(Self::new(parentchain_api, enclave_api, parentchain_init_params)) + } + + pub fn parentchain_api(&self) -> &ParentchainApi { + &self.parentchain_api + } +} + +impl HandleParentchain + for ParentchainHandler +where + ParentchainApi: ChainApi, + EnclaveApi: Sidechain + EnclaveBase, +{ + fn init_parentchain_components(&self) -> ServiceResult
{ + Ok(self + .enclave_api + .init_parentchain_components(self.parentchain_init_params.clone())?) + } + + fn sync_parentchain(&self, last_synced_header: Header) -> ServiceResult
{ + trace!("Getting current head"); + let curr_block: SignedBlock = self + .parentchain_api + .last_finalized_block()? + .ok_or(Error::MissingLastFinalizedBlock)?; + let curr_block_number = curr_block.block.header.number; + + let mut until_synced_header = last_synced_header; + loop { + let block_chunk_to_sync = self.parentchain_api.get_blocks( + until_synced_header.number + 1, + min(until_synced_header.number + BLOCK_SYNC_BATCH_SIZE, curr_block_number), + )?; + println!("[+] Found {} block(s) to sync", block_chunk_to_sync.len()); + if block_chunk_to_sync.is_empty() { + return Ok(until_synced_header) + } + + self.enclave_api.sync_parentchain(block_chunk_to_sync.as_slice(), 0)?; + + until_synced_header = block_chunk_to_sync + .last() + .map(|b| b.block.header.clone()) + .ok_or(Error::EmptyChunk)?; + println!( + "Synced {} out of {} finalized parentchain blocks", + until_synced_header.number, curr_block_number, + ) + } + } + + fn trigger_parentchain_block_import(&self) -> ServiceResult<()> { + Ok(self.enclave_api.trigger_parentchain_block_import()?) + } + + fn sync_and_import_parentchain_until( + &self, + last_synced_header: &Header, + until_header: &Header, + ) -> ServiceResult
{ + let mut last_synced_header = last_synced_header.clone(); + + while last_synced_header.number() < until_header.number() { + last_synced_header = self.sync_parentchain(last_synced_header)?; + } + self.trigger_parentchain_block_import()?; + + Ok(last_synced_header) + } +} diff --git a/tee-worker/service/src/prometheus_metrics.rs b/tee-worker/service/src/prometheus_metrics.rs new file mode 100644 index 0000000000..6d41e120f0 --- /dev/null +++ b/tee-worker/service/src/prometheus_metrics.rs @@ -0,0 +1,172 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +//! Service for prometheus metrics, hosted on a http server. + +#[cfg(feature = "teeracle")] +use crate::teeracle::teeracle_metrics::update_teeracle_metrics; + +use crate::{ + account_funding::EnclaveAccountInfo, + error::{Error, ServiceResult}, +}; +use async_trait::async_trait; +use itp_enclave_metrics::EnclaveMetric; +use lazy_static::lazy_static; +use log::*; +use prometheus::{proto::MetricFamily, register_int_gauge, IntGauge}; +use std::{net::SocketAddr, sync::Arc}; +use warp::{Filter, Rejection, Reply}; + +lazy_static! { + /// Register all the prometheus metrics we want to monitor (aside from the default process ones). + + static ref ENCLAVE_ACCOUNT_FREE_BALANCE: IntGauge = + register_int_gauge!("integritee_worker_enclave_account_free_balance", "Free balance of the enclave account") + .unwrap(); + static ref ENCLAVE_SIDECHAIN_BLOCK_HEIGHT: IntGauge = + register_int_gauge!("integritee_worker_enclave_sidechain_block_height", "Enclave sidechain block height") + .unwrap(); + static ref ENCLAVE_SIDECHAIN_TOP_POOL_SIZE: IntGauge = + register_int_gauge!("integritee_worker_enclave_sidechain_top_pool_size", "Enclave sidechain top pool size") + .unwrap(); +} + +pub async fn start_metrics_server( + metrics_handler: Arc, + port: u16, +) -> ServiceResult<()> +where + MetricsHandler: HandleMetrics + Send + Sync + 'static, +{ + let metrics_route = warp::path!("metrics").and_then(move || { + let handler_clone = metrics_handler.clone(); + async move { handler_clone.handle_metrics().await } + }); + let socket_addr: SocketAddr = ([0, 0, 0, 0], port).into(); + + info!("Running prometheus metrics server on: {:?}", socket_addr); + warp::serve(metrics_route).run(socket_addr).await; + + info!("Prometheus metrics server shut down"); + Ok(()) +} + +#[async_trait] +pub trait HandleMetrics { + type ReplyType: Reply; + + async fn handle_metrics(&self) -> Result; +} + +/// Metrics handler implementation. +pub struct MetricsHandler { + enclave_wallet: Arc, +} + +#[async_trait] +impl HandleMetrics for MetricsHandler +where + Wallet: EnclaveAccountInfo + Send + Sync, +{ + type ReplyType = String; + + async fn handle_metrics(&self) -> Result { + self.update_metrics().await; + + let default_metrics = match gather_metrics_into_reply(&prometheus::gather()) { + Ok(r) => r, + Err(e) => { + error!("Failed to gather prometheus metrics: {:?}", e); + String::default() + }, + }; + + Ok(default_metrics) + } +} + +impl MetricsHandler +where + Wallet: EnclaveAccountInfo + Send + Sync, +{ + pub fn new(enclave_wallet: Arc) -> Self { + MetricsHandler { enclave_wallet } + } + + async fn update_metrics(&self) { + match self.enclave_wallet.free_balance() { + Ok(b) => { + ENCLAVE_ACCOUNT_FREE_BALANCE.set(b as i64); + }, + Err(e) => { + error!("Failed to fetch free balance metric, value will not be updated: {:?}", e); + }, + } + } +} + +fn gather_metrics_into_reply(metrics: &[MetricFamily]) -> ServiceResult { + use prometheus::Encoder; + let encoder = prometheus::TextEncoder::new(); + + let mut buffer = Vec::new(); + encoder.encode(metrics, &mut buffer).map_err(|e| { + Error::Custom(format!("Failed to encode prometheus metrics: {:?}", e).into()) + })?; + + let result_string = String::from_utf8(buffer).map_err(|e| { + Error::Custom( + format!("Failed to convert Prometheus encoded metrics to UTF8: {:?}", e).into(), + ) + })?; + + Ok(result_string) +} + +/// Trait to receive metric updates from inside the enclave. +pub trait ReceiveEnclaveMetrics { + fn receive_enclave_metric(&self, metric: EnclaveMetric) -> ServiceResult<()>; +} + +pub struct EnclaveMetricsReceiver; + +impl ReceiveEnclaveMetrics for EnclaveMetricsReceiver { + fn receive_enclave_metric(&self, metric: EnclaveMetric) -> ServiceResult<()> { + match metric { + EnclaveMetric::SetSidechainBlockHeight(h) => { + ENCLAVE_SIDECHAIN_BLOCK_HEIGHT.set(h as i64); + }, + EnclaveMetric::TopPoolSizeSet(pool_size) => { + ENCLAVE_SIDECHAIN_TOP_POOL_SIZE.set(pool_size as i64); + }, + EnclaveMetric::TopPoolSizeIncrement => { + ENCLAVE_SIDECHAIN_TOP_POOL_SIZE.inc(); + }, + EnclaveMetric::TopPoolSizeDecrement => { + ENCLAVE_SIDECHAIN_TOP_POOL_SIZE.dec(); + }, + #[cfg(feature = "teeracle")] + EnclaveMetric::ExchangeRateOracle(m) => update_teeracle_metrics(m)?, + #[cfg(not(feature = "teeracle"))] + EnclaveMetric::ExchangeRateOracle(_) => { + error!("Received Teeracle metric, but Teeracle feature is not enabled, ignoring metric item.") + }, + } + Ok(()) + } +} diff --git a/tee-worker/service/src/setup.rs b/tee-worker/service/src/setup.rs new file mode 100644 index 0000000000..77e5c47504 --- /dev/null +++ b/tee-worker/service/src/setup.rs @@ -0,0 +1,198 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + Copyright (C) 2017-2019 Baidu, Inc. All Rights Reserved. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +use crate::error::{Error, ServiceResult}; +use codec::Encode; +use itp_enclave_api::{enclave_base::EnclaveBase, Enclave}; +use itp_settings::files::{ + LAST_SLOT_BIN, LIGHT_CLIENT_DB, SHARDS_PATH, SHIELDING_KEY_FILE, SIDECHAIN_STORAGE_PATH, + SIGNING_KEY_FILE, +}; +use itp_types::ShardIdentifier; +use log::*; +use std::{fs, fs::File, path::Path}; + +/// Purge all worker files from the current working directory (cwd). +pub(crate) fn purge_files_from_cwd() -> ServiceResult<()> { + let current_directory = std::env::current_dir().map_err(|e| Error::Custom(e.into()))?; + println!("[+] Performing a clean reset of the worker"); + + println!("[+] Purge all files from previous runs"); + purge_files(¤t_directory)?; + + Ok(()) +} + +/// Initializes the shard and generates the key files. +pub(crate) fn initialize_shard_and_keys( + enclave: &Enclave, + shard_identifier: &ShardIdentifier, +) -> ServiceResult<()> { + println!("[+] Initialize the shard"); + init_shard(enclave, shard_identifier); + + println!("[+] Generate key files"); + generate_signing_key_file(enclave); + generate_shielding_key_file(enclave); + + Ok(()) +} + +pub(crate) fn init_shard(enclave: &Enclave, shard_identifier: &ShardIdentifier) { + match enclave.init_shard(shard_identifier.encode()) { + Err(e) => { + println!("Failed to initialize shard {:?}: {:?}", shard_identifier, e); + }, + Ok(_) => { + println!("Successfully initialized shard {:?}", shard_identifier); + }, + } +} + +pub(crate) fn generate_signing_key_file(enclave: &Enclave) { + info!("*** Get the signing key from the TEE\n"); + let pubkey = enclave.get_ecc_signing_pubkey().unwrap(); + debug!("[+] Signing key raw: {:?}", pubkey); + match fs::write(SIGNING_KEY_FILE, pubkey) { + Err(x) => { + error!("[-] Failed to write '{}'. {}", SIGNING_KEY_FILE, x); + }, + _ => { + println!("[+] File '{}' written successfully", SIGNING_KEY_FILE); + }, + } +} + +pub(crate) fn generate_shielding_key_file(enclave: &Enclave) { + info!("*** Get the public key from the TEE\n"); + let pubkey = enclave.get_rsa_shielding_pubkey().unwrap(); + let file = File::create(SHIELDING_KEY_FILE).unwrap(); + match serde_json::to_writer(file, &pubkey) { + Err(x) => { + error!("[-] Failed to write '{}'. {}", SHIELDING_KEY_FILE, x); + }, + _ => { + println!("[+] File '{}' written successfully", SHIELDING_KEY_FILE); + }, + } +} + +/// Purge all worker files in a given path. +fn purge_files(root_directory: &Path) -> ServiceResult<()> { + remove_dir_if_it_exists(root_directory, SHARDS_PATH)?; + remove_dir_if_it_exists(root_directory, SIDECHAIN_STORAGE_PATH)?; + + remove_file_if_it_exists(root_directory, LAST_SLOT_BIN)?; + remove_file_if_it_exists(root_directory, LIGHT_CLIENT_DB)?; + remove_file_if_it_exists(root_directory, light_client_backup_file().as_str())?; + + Ok(()) +} + +fn remove_dir_if_it_exists(root_directory: &Path, dir_name: &str) -> ServiceResult<()> { + let directory_path = root_directory.join(dir_name); + if directory_path.exists() { + fs::remove_dir_all(directory_path).map_err(|e| Error::Custom(e.into()))?; + } + Ok(()) +} + +fn remove_file_if_it_exists(root_directory: &Path, file_name: &str) -> ServiceResult<()> { + let file = root_directory.join(file_name); + if file.exists() { + fs::remove_file(file).map_err(|e| Error::Custom(e.into()))?; + } + Ok(()) +} + +fn light_client_backup_file() -> String { + format!("{}.1", LIGHT_CLIENT_DB) +} + +#[cfg(test)] +mod tests { + use super::*; + use itp_settings::files::SHARDS_PATH; + use std::{fs, path::PathBuf}; + + #[test] + fn purge_files_deletes_all_relevant_files() { + let test_directory_handle = + TestDirectoryHandle::new(PathBuf::from("test_purge_files_deletes_all_relevant_files")); + let root_directory = test_directory_handle.path(); + + let shards_path = root_directory.join(SHARDS_PATH); + fs::create_dir_all(&shards_path).unwrap(); + fs::File::create(&shards_path.join("state_1.bin")).unwrap(); + fs::File::create(&shards_path.join("state_2.bin")).unwrap(); + + let sidechain_db_path = root_directory.join(SIDECHAIN_STORAGE_PATH); + fs::create_dir_all(&sidechain_db_path).unwrap(); + fs::File::create(&sidechain_db_path.join("sidechain_db_1.bin")).unwrap(); + fs::File::create(&sidechain_db_path.join("sidechain_db_2.bin")).unwrap(); + fs::File::create(&sidechain_db_path.join("sidechain_db_3.bin")).unwrap(); + + fs::File::create(&root_directory.join(LAST_SLOT_BIN)).unwrap(); + fs::File::create(&root_directory.join(LIGHT_CLIENT_DB)).unwrap(); + fs::File::create(&root_directory.join(light_client_backup_file())).unwrap(); + + purge_files(&root_directory).unwrap(); + + assert!(!shards_path.exists()); + assert!(!sidechain_db_path.exists()); + assert!(!root_directory.join(LAST_SLOT_BIN).exists()); + assert!(!root_directory.join(LIGHT_CLIENT_DB).exists()); + assert!(!root_directory.join(light_client_backup_file()).exists()); + } + + #[test] + fn purge_files_succeeds_when_no_files_exist() { + let test_directory_handle = TestDirectoryHandle::new(PathBuf::from( + "test_purge_files_succeeds_when_no_files_exist", + )); + let root_directory = test_directory_handle.path(); + + assert!(purge_files(&root_directory).is_ok()); + } + + /// Directory handle to automatically initialize a directory + /// and upon dropping the reference, removing it again. + struct TestDirectoryHandle { + path: PathBuf, + } + + impl TestDirectoryHandle { + pub fn new(path: PathBuf) -> Self { + let test_path = std::env::current_dir().unwrap().join(&path); + fs::create_dir_all(&test_path).unwrap(); + TestDirectoryHandle { path: test_path } + } + + pub fn path(&self) -> &PathBuf { + &self.path + } + } + + impl Drop for TestDirectoryHandle { + fn drop(&mut self) { + if self.path.exists() { + fs::remove_dir_all(&self.path).unwrap(); + } + } + } +} diff --git a/tee-worker/service/src/sidechain_setup.rs b/tee-worker/service/src/sidechain_setup.rs new file mode 100644 index 0000000000..9827cd53a2 --- /dev/null +++ b/tee-worker/service/src/sidechain_setup.rs @@ -0,0 +1,125 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +use crate::{ + error::{Error, ServiceResult}, + parentchain_handler::HandleParentchain, + Config, +}; +use futures::executor::block_on; +use itp_enclave_api::{ + direct_request::DirectRequest, enclave_base::EnclaveBase, sidechain::Sidechain, +}; +use itp_settings::{ + files::{SIDECHAIN_PURGE_INTERVAL, SIDECHAIN_PURGE_LIMIT}, + sidechain::SLOT_DURATION, +}; +use itp_types::Header; +use its_consensus_slots::start_slot_worker; +use its_primitives::types::block::SignedBlock as SignedSidechainBlock; +use its_storage::{interface::FetchBlocks, start_sidechain_pruning_loop, BlockPruner}; +use log::*; +use std::{sync::Arc, thread}; +use tokio::runtime::Handle; + +pub(crate) fn sidechain_start_untrusted_rpc_server( + config: &Config, + enclave: Arc, + sidechain_storage: Arc, + tokio_handle: Handle, +) where + Enclave: DirectRequest + Clone, + SidechainStorage: BlockPruner + FetchBlocks + Sync + Send + 'static, +{ + let untrusted_url = config.untrusted_worker_url(); + println!("[+] Untrusted RPC server listening on {}", &untrusted_url); + let _untrusted_rpc_join_handle = tokio_handle.spawn(async move { + itc_rpc_server::run_server(&untrusted_url, enclave, sidechain_storage) + .await + .unwrap(); + }); +} + +pub(crate) fn sidechain_init_block_production( + enclave: Arc, + register_enclave_xt_header: &Header, + we_are_primary_validateer: bool, + parentchain_handler: Arc, + sidechain_storage: Arc, + last_synced_header: &Header, +) -> ServiceResult
+where + Enclave: EnclaveBase + Sidechain, + SidechainStorage: BlockPruner + FetchBlocks + Sync + Send + 'static, + ParentchainHandler: HandleParentchain, +{ + // If we're the first validateer to register, also trigger parentchain block import. + let mut updated_header: Option
= None; + + if we_are_primary_validateer { + info!( + "We're the first validateer to be registered, syncing parentchain blocks until the one we have registered ourselves on." + ); + updated_header = + Some(parentchain_handler.sync_and_import_parentchain_until( + last_synced_header, + register_enclave_xt_header, + )?); + } + + // ------------------------------------------------------------------------ + // Initialize sidechain components (has to be AFTER init_parentchain_components() + enclave.init_enclave_sidechain_components().unwrap(); + + // ------------------------------------------------------------------------ + // Start interval sidechain block production (execution of trusted calls, sidechain block production). + let sidechain_enclave_api = enclave; + println!("[+] Spawning thread for sidechain block production"); + thread::Builder::new() + .name("interval_block_production_timer".to_owned()) + .spawn(move || { + let future = start_slot_worker( + || execute_trusted_calls(sidechain_enclave_api.as_ref()), + SLOT_DURATION, + ); + block_on(future); + println!("[!] Sidechain block production loop has terminated"); + }) + .map_err(|e| Error::Custom(Box::new(e)))?; + + // ------------------------------------------------------------------------ + // start sidechain pruning loop + thread::Builder::new() + .name("sidechain_pruning_loop".to_owned()) + .spawn(move || { + start_sidechain_pruning_loop( + &sidechain_storage, + SIDECHAIN_PURGE_INTERVAL, + SIDECHAIN_PURGE_LIMIT, + ); + }) + .map_err(|e| Error::Custom(Box::new(e)))?; + + Ok(updated_header.unwrap_or_else(|| last_synced_header.clone())) +} + +/// Execute trusted operations in the enclave. +fn execute_trusted_calls(enclave_api: &E) { + if let Err(e) = enclave_api.execute_trusted_calls() { + error!("{:?}", e); + }; +} diff --git a/tee-worker/service/src/sync_block_broadcaster.rs b/tee-worker/service/src/sync_block_broadcaster.rs new file mode 100644 index 0000000000..b0752c900d --- /dev/null +++ b/tee-worker/service/src/sync_block_broadcaster.rs @@ -0,0 +1,57 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + Copyright (C) 2017-2019 Baidu, Inc. All Rights Reserved. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +#[cfg(test)] +use mockall::predicate::*; +#[cfg(test)] +use mockall::*; + +use crate::{ + globals::tokio_handle::GetTokioHandle, + worker::{AsyncBlockBroadcaster, WorkerResult}, +}; +use its_primitives::types::block::SignedBlock as SignedSidechainBlock; +use std::sync::Arc; + +/// Allows to broadcast blocks, does it in a synchronous (i.e. blocking) manner +#[cfg_attr(test, automock)] +pub trait BroadcastBlocks { + fn broadcast_blocks(&self, blocks: Vec) -> WorkerResult<()>; +} + +pub struct SyncBlockBroadcaster { + tokio_handle: Arc, + worker: Arc, +} + +impl SyncBlockBroadcaster { + pub fn new(tokio_handle: Arc, worker: Arc) -> Self { + SyncBlockBroadcaster { tokio_handle, worker } + } +} + +impl BroadcastBlocks for SyncBlockBroadcaster +where + T: GetTokioHandle, + W: AsyncBlockBroadcaster, +{ + fn broadcast_blocks(&self, blocks: Vec) -> WorkerResult<()> { + let handle = self.tokio_handle.get_handle(); + handle.block_on(self.worker.broadcast_blocks(blocks)) + } +} diff --git a/tee-worker/service/src/sync_state.rs b/tee-worker/service/src/sync_state.rs new file mode 100644 index 0000000000..0a924662ba --- /dev/null +++ b/tee-worker/service/src/sync_state.rs @@ -0,0 +1,96 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +//! Request state keys from a fellow validateer. + +use crate::{ + enclave::tls_ra::enclave_request_state_provisioning, + error::{Error, ServiceResult as Result}, +}; +use futures::executor; +use itc_rpc_client::direct_client::{DirectApi, DirectClient as DirectWorkerApi}; +use itp_enclave_api::{enclave_base::EnclaveBase, remote_attestation::TlsRemoteAttestation}; +use itp_node_api::api_client::PalletTeerexApi; +use itp_settings::worker_mode::{ProvideWorkerMode, WorkerMode}; +use itp_types::ShardIdentifier; +use sgx_types::sgx_quote_sign_type_t; +use std::string::String; + +pub(crate) fn sync_state< + E: TlsRemoteAttestation + EnclaveBase, + NodeApi: PalletTeerexApi, + WorkerModeProvider: ProvideWorkerMode, +>( + node_api: &NodeApi, + shard: &ShardIdentifier, + enclave_api: &E, + skip_ra: bool, +) { + // FIXME: we now assume that keys are equal for all shards. + let provider_url = match WorkerModeProvider::worker_mode() { + WorkerMode::Sidechain => + executor::block_on(get_author_url_of_last_finalized_sidechain_block(node_api, shard)) + .expect("Author of last finalized sidechain block could not be found"), + _ => executor::block_on(get_enclave_url_of_first_registered(node_api, enclave_api)) + .expect("Author of last finalized sidechain block could not be found"), + }; + + println!("Requesting state provisioning from worker at {}", &provider_url); + + enclave_request_state_provisioning( + enclave_api, + sgx_quote_sign_type_t::SGX_UNLINKABLE_SIGNATURE, + &provider_url, + shard, + skip_ra, + ) + .unwrap(); + println!("[+] State provisioning successfully performed."); +} + +/// Returns the url of the last sidechain block author that has been stored +/// in the parentchain state as "worker for shard". +/// +/// Note: The sidechainblock author will only change whenever a new parentchain block is +/// produced. And even then, it might be the same as the last block. So if several workers +/// are started in a timely manner, they will all get the same url. +async fn get_author_url_of_last_finalized_sidechain_block( + node_api: &NodeApi, + shard: &ShardIdentifier, +) -> Result { + let enclave = node_api + .worker_for_shard(shard, None)? + .ok_or_else(|| Error::NoWorkerForShardFound(*shard))?; + let worker_api_direct = DirectWorkerApi::new(enclave.url); + Ok(worker_api_direct.get_mu_ra_url()?) +} + +/// Returns the url of the first Enclave that matches our own MRENCLAVE. +/// +/// This should be run before we register ourselves as enclave, to ensure we don't get our own url. +async fn get_enclave_url_of_first_registered( + node_api: &NodeApi, + enclave_api: &EnclaveApi, +) -> Result { + let self_mr_enclave = enclave_api.get_mrenclave()?; + let first_enclave = node_api + .all_enclaves(None)? + .into_iter() + .find(|e| e.mr_enclave == self_mr_enclave) + .ok_or(Error::NoPeerWorkerFound)?; + let worker_api_direct = DirectWorkerApi::new(first_enclave.url); + Ok(worker_api_direct.get_mu_ra_url()?) +} diff --git a/tee-worker/service/src/teeracle/interval_scheduling.rs b/tee-worker/service/src/teeracle/interval_scheduling.rs new file mode 100644 index 0000000000..5cf60b3cf1 --- /dev/null +++ b/tee-worker/service/src/teeracle/interval_scheduling.rs @@ -0,0 +1,46 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +use std::{ + thread, + time::{Duration, Instant}, +}; + +/// Schedules a task on perpetually looping intervals. +/// +/// In case the task takes longer than is scheduled by the interval duration, +/// the interval timing will drift. The task is responsible for +/// ensuring it does not use up more time than is scheduled. +pub(super) fn schedule_on_repeating_intervals(task: T, interval_duration: Duration) +where + T: Fn(), +{ + let mut interval_start = Instant::now(); + loop { + let elapsed = interval_start.elapsed(); + + if elapsed >= interval_duration { + // update interval time + interval_start = Instant::now(); + task(); + } else { + // sleep for the rest of the interval + let sleep_time = interval_duration - elapsed; + thread::sleep(sleep_time); + } + } +} diff --git a/tee-worker/service/src/teeracle/mod.rs b/tee-worker/service/src/teeracle/mod.rs new file mode 100644 index 0000000000..321655dede --- /dev/null +++ b/tee-worker/service/src/teeracle/mod.rs @@ -0,0 +1,100 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +use crate::teeracle::interval_scheduling::schedule_on_repeating_intervals; +use codec::{Decode, Encode}; +use itp_enclave_api::teeracle_api::TeeracleApi; +use itp_node_api::api_client::ParentchainApi; +use itp_settings::teeracle::DEFAULT_MARKET_DATA_UPDATE_INTERVAL; +use log::*; +use sp_runtime::OpaqueExtrinsic; +use std::time::Duration; +use substrate_api_client::XtStatus; +use teeracle_metrics::{increment_number_of_request_failures, set_extrinsics_inclusion_success}; +use tokio::runtime::Handle; + +pub(crate) mod interval_scheduling; +pub(crate) mod teeracle_metrics; + +/// Send extrinsic to chain according to the market data update interval in the settings +/// with the current market data (for now only exchange rate). +pub(crate) fn start_interval_market_update( + api: &ParentchainApi, + maybe_interval: Option, + enclave_api: &E, + tokio_handle: &Handle, +) { + let interval = maybe_interval.unwrap_or(DEFAULT_MARKET_DATA_UPDATE_INTERVAL); + info!("Starting teeracle interval market data update, interval of {:?}", interval); + + schedule_on_repeating_intervals( + || { + execute_update_market(api, enclave_api, tokio_handle); + }, + interval, + ); +} + +fn execute_update_market( + node_api: &ParentchainApi, + enclave: &E, + tokio_handle: &Handle, +) { + // Get market data for usd (hardcoded) + let updated_extrinsic = match enclave.update_market_data_xt("TEER", "USD") { + Err(e) => { + error!("{:?}", e); + increment_number_of_request_failures(); + return + }, + Ok(r) => r, + }; + + let extrinsics: Vec = match Decode::decode(&mut updated_extrinsic.as_slice()) { + Ok(calls) => calls, + Err(e) => { + error!("Failed to decode opaque extrinsic(s): {:?}: ", e); + return + }, + }; + + // Send the extrinsics to the parentchain and wait for InBlock confirmation. + for call in extrinsics.into_iter() { + let node_api_clone = node_api.clone(); + tokio_handle.spawn(async move { + let mut hex_encoded_extrinsic = hex::encode(call.encode()); + hex_encoded_extrinsic.insert_str(0, "0x"); + debug!("Hex encoded extrinsic to be sent: {}", hex_encoded_extrinsic); + + println!("[>] Update the exchange rate (send the extrinsic)"); + let extrinsic_hash = + match node_api_clone.send_extrinsic(hex_encoded_extrinsic, XtStatus::InBlock) { + Err(e) => { + error!("Failed to send extrinsic: {:?}", e); + set_extrinsics_inclusion_success(false); + return + }, + Ok(hash) => { + set_extrinsics_inclusion_success(true); + hash + }, + }; + + println!("[<] Extrinsic got included into a block. Hash: {:?}\n", extrinsic_hash); + }); + } +} diff --git a/tee-worker/service/src/teeracle/teeracle_metrics.rs b/tee-worker/service/src/teeracle/teeracle_metrics.rs new file mode 100644 index 0000000000..495e6e648f --- /dev/null +++ b/tee-worker/service/src/teeracle/teeracle_metrics.rs @@ -0,0 +1,76 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +use crate::{error::ServiceResult, Error}; +use itp_enclave_metrics::ExchangeRateOracleMetric; +use lazy_static::lazy_static; +use prometheus::{ + register_gauge_vec, register_int_counter, register_int_counter_vec, register_int_gauge, + register_int_gauge_vec, GaugeVec, IntCounter, IntCounterVec, IntGauge, IntGaugeVec, +}; + +lazy_static! { + /// Register Teeracle specific metrics + + static ref EXCHANGE_RATE: GaugeVec = + register_gauge_vec!("integritee_teeracle_exchange_rate", "Exchange rates partitioned into source and trading pair", &["source", "trading_pair"]) + .unwrap(); + static ref RESPONSE_TIME: IntGaugeVec = + register_int_gauge_vec!("integritee_teeracle_response_times", "Response times in ms for requests that the oracle makes", &["source"]) + .unwrap(); + static ref NUMBER_OF_REQUESTS: IntCounterVec = + register_int_counter_vec!("integritee_teeracle_number_of_requests", "Number of requests made per source", &["source"]) + .unwrap(); + + static ref NUMBER_OF_REQUEST_FAILURES: IntCounter = + register_int_counter!("integritee_teeracle_request_failures", "Number of requests that failed") + .unwrap(); + + static ref EXTRINSIC_INCLUSION_SUCCESS: IntGauge = + register_int_gauge!("integritee_teeracle_extrinsic_inclusion_success", "1 if extrinsics was successfully finalized, 0 if not") + .unwrap(); +} + +pub(super) fn increment_number_of_request_failures() { + NUMBER_OF_REQUEST_FAILURES.inc(); +} + +pub(super) fn set_extrinsics_inclusion_success(is_successful: bool) { + let success_values = i64::from(is_successful); + EXTRINSIC_INCLUSION_SUCCESS.set(success_values); +} + +pub fn update_teeracle_metrics(metric: ExchangeRateOracleMetric) -> ServiceResult<()> { + match metric { + ExchangeRateOracleMetric::ExchangeRate(source, trading_pair, exchange_rate) => + EXCHANGE_RATE + .get_metric_with_label_values(&[source.as_str(), trading_pair.as_str()]) + .map(|m| m.set(exchange_rate.to_num())) + .map_err(|e| Error::Custom(e.into()))?, + + ExchangeRateOracleMetric::ResponseTime(source, t) => RESPONSE_TIME + .get_metric_with_label_values(&[source.as_str()]) + .map(|m| m.set(t as i64)) + .map_err(|e| Error::Custom(e.into()))?, + + ExchangeRateOracleMetric::NumberRequestsIncrement(source) => NUMBER_OF_REQUESTS + .get_metric_with_label_values(&[source.as_str()]) + .map(|m| m.inc()) + .map_err(|e| Error::Custom(e.into()))?, + }; + Ok(()) +} diff --git a/tee-worker/service/src/tests/commons.rs b/tee-worker/service/src/tests/commons.rs new file mode 100644 index 0000000000..96ba10d595 --- /dev/null +++ b/tee-worker/service/src/tests/commons.rs @@ -0,0 +1,54 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +use serde_derive::{Deserialize, Serialize}; +use sgx_types::*; +use std::str; + +#[cfg(test)] +use crate::config::Config; + +#[derive(Debug, Serialize, Deserialize)] +pub struct Message { + pub account: String, + pub amount: u32, + pub sha256: sgx_sha256_hash_t, +} + +#[cfg(test)] +pub fn local_worker_config( + worker_url: String, + untrusted_worker_port: String, + mu_ra_port: String, +) -> Config { + let mut url = worker_url.split(':'); + Config::new( + Default::default(), + Default::default(), + url.next().unwrap().into(), + None, + url.next().unwrap().into(), + None, + untrusted_worker_port, + None, + mu_ra_port, + false, + "8787".to_string(), + "4545".to_string(), + None, + ) +} diff --git a/tee-worker/service/src/tests/mock.rs b/tee-worker/service/src/tests/mock.rs new file mode 100644 index 0000000000..664005eaa1 --- /dev/null +++ b/tee-worker/service/src/tests/mock.rs @@ -0,0 +1,59 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +use itp_node_api::api_client::{ApiResult, PalletTeerexApi}; +use itp_types::{Enclave, ShardIdentifier, H256 as Hash}; + +pub struct TestNodeApi; + +pub const W1_URL: &str = "127.0.0.1:22222"; +pub const W2_URL: &str = "127.0.0.1:33333"; + +pub fn enclaves() -> Vec { + vec![ + Enclave::new([0; 32].into(), [1; 32], 1, format!("wss://{}", W1_URL)), + Enclave::new([2; 32].into(), [3; 32], 2, format!("wss://{}", W2_URL)), + ] +} + +impl PalletTeerexApi for TestNodeApi { + fn enclave(&self, index: u64, _at_block: Option) -> ApiResult> { + Ok(Some(enclaves().remove(index as usize))) + } + fn enclave_count(&self, _at_block: Option) -> ApiResult { + unreachable!() + } + + fn all_enclaves(&self, _at_block: Option) -> ApiResult> { + Ok(enclaves()) + } + + fn worker_for_shard( + &self, + _: &ShardIdentifier, + _at_block: Option, + ) -> ApiResult> { + unreachable!() + } + fn latest_ipfs_hash( + &self, + _: &ShardIdentifier, + _at_block: Option, + ) -> ApiResult> { + unreachable!() + } +} diff --git a/tee-worker/service/src/tests/mocks/broadcast_blocks_mock.rs b/tee-worker/service/src/tests/mocks/broadcast_blocks_mock.rs new file mode 100644 index 0000000000..2df5f65506 --- /dev/null +++ b/tee-worker/service/src/tests/mocks/broadcast_blocks_mock.rs @@ -0,0 +1,28 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +use crate::{sync_block_broadcaster::BroadcastBlocks, worker::WorkerResult}; +use its_primitives::types::block::SignedBlock as SignedSidechainBlock; +use std::vec::Vec; + +pub struct BroadcastBlocksMock; + +impl BroadcastBlocks for BroadcastBlocksMock { + fn broadcast_blocks(&self, _blocks: Vec) -> WorkerResult<()> { + Ok(()) + } +} diff --git a/tee-worker/service/src/tests/mocks/direct_request_mock.rs b/tee-worker/service/src/tests/mocks/direct_request_mock.rs new file mode 100644 index 0000000000..a2c572dfc6 --- /dev/null +++ b/tee-worker/service/src/tests/mocks/direct_request_mock.rs @@ -0,0 +1,26 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +use itp_enclave_api::{direct_request::DirectRequest, EnclaveResult}; + +pub struct DirectRequestMock; + +impl DirectRequest for DirectRequestMock { + fn rpc(&self, request: Vec) -> EnclaveResult> { + Ok(request) + } +} diff --git a/tee-worker/service/src/tests/mocks/enclave_api_mock.rs b/tee-worker/service/src/tests/mocks/enclave_api_mock.rs new file mode 100644 index 0000000000..c4952f4cdb --- /dev/null +++ b/tee-worker/service/src/tests/mocks/enclave_api_mock.rs @@ -0,0 +1,99 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +use codec::{Decode, Encode}; +use core::fmt::Debug; +use frame_support::sp_runtime::traits::Block as ParentchainBlockTrait; +use itc_parentchain::primitives::{ + ParentchainInitParams, + ParentchainInitParams::{Parachain, Solochain}, +}; +use itp_enclave_api::{enclave_base::EnclaveBase, sidechain::Sidechain, EnclaveResult}; +use itp_settings::worker::MR_ENCLAVE_SIZE; +use sgx_crypto_helper::rsa3072::Rsa3072PubKey; +use sp_core::ed25519; + +/// mock for EnclaveBase - use in tests +pub struct EnclaveMock; + +impl EnclaveBase for EnclaveMock { + fn init(&self, _mu_ra_url: &str, _untrusted_url: &str) -> EnclaveResult<()> { + Ok(()) + } + + fn init_enclave_sidechain_components(&self) -> EnclaveResult<()> { + Ok(()) + } + + fn init_direct_invocation_server(&self, _rpc_server_addr: String) -> EnclaveResult<()> { + unreachable!() + } + + fn init_parentchain_components( + &self, + params: ParentchainInitParams, + ) -> EnclaveResult
{ + let genesis_header_encoded = match params { + Solochain { params } => params.genesis_header.encode(), + Parachain { params } => params.genesis_header.encode(), + }; + let header = Header::decode(&mut genesis_header_encoded.as_slice())?; + Ok(header) + } + + fn init_shard(&self, _shard: Vec) -> EnclaveResult<()> { + unimplemented!() + } + + fn trigger_parentchain_block_import(&self) -> EnclaveResult<()> { + unimplemented!() + } + + fn set_nonce(&self, _: u32) -> EnclaveResult<()> { + unimplemented!() + } + + fn set_node_metadata(&self, _metadata: Vec) -> EnclaveResult<()> { + todo!() + } + + fn get_rsa_shielding_pubkey(&self) -> EnclaveResult { + unreachable!() + } + + fn get_ecc_signing_pubkey(&self) -> EnclaveResult { + unreachable!() + } + + fn get_mrenclave(&self) -> EnclaveResult<[u8; MR_ENCLAVE_SIZE]> { + Ok([1u8; MR_ENCLAVE_SIZE]) + } +} + +impl Sidechain for EnclaveMock { + fn sync_parentchain( + &self, + _blocks: &[sp_runtime::generic::SignedBlock], + _nonce: u32, + ) -> EnclaveResult<()> { + Ok(()) + } + + fn execute_trusted_calls(&self) -> EnclaveResult<()> { + todo!() + } +} diff --git a/tee-worker/service/src/tests/mocks/initialization_handler_mock.rs b/tee-worker/service/src/tests/mocks/initialization_handler_mock.rs new file mode 100644 index 0000000000..f65439d64b --- /dev/null +++ b/tee-worker/service/src/tests/mocks/initialization_handler_mock.rs @@ -0,0 +1,36 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +use crate::{IsInitialized, TrackInitialization}; + +pub struct TrackInitializationMock; + +impl TrackInitialization for TrackInitializationMock { + fn registered_on_parentchain(&self) {} + + fn sidechain_block_produced(&self) {} + + fn worker_for_shard_registered(&self) {} +} + +pub struct IsInitializedMock; + +impl IsInitialized for IsInitializedMock { + fn is_initialized(&self) -> bool { + true + } +} diff --git a/tee-worker/service/src/tests/mocks/mod.rs b/tee-worker/service/src/tests/mocks/mod.rs new file mode 100644 index 0000000000..cfe0d6fc76 --- /dev/null +++ b/tee-worker/service/src/tests/mocks/mod.rs @@ -0,0 +1,23 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +pub mod broadcast_blocks_mock; +pub mod direct_request_mock; +pub mod enclave_api_mock; +pub mod initialization_handler_mock; +pub mod parentchain_api_mock; +pub mod update_worker_peers_mock; diff --git a/tee-worker/service/src/tests/mocks/parentchain_api_mock.rs b/tee-worker/service/src/tests/mocks/parentchain_api_mock.rs new file mode 100644 index 0000000000..5f02df4f04 --- /dev/null +++ b/tee-worker/service/src/tests/mocks/parentchain_api_mock.rs @@ -0,0 +1,87 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +use itc_parentchain_test::{ + parentchain_block_builder::ParentchainBlockBuilder, + parentchain_header_builder::ParentchainHeaderBuilder, +}; +use itp_node_api::api_client::{ApiResult, ChainApi, StorageProof}; +use itp_types::{Header, SignedBlock, H256}; +use sp_finality_grandpa::AuthorityList; + +pub struct ParentchainApiMock { + parentchain: Vec, +} + +impl ParentchainApiMock { + pub(crate) fn new() -> Self { + ParentchainApiMock { parentchain: Vec::new() } + } + + /// Initializes parentchain with a default block chain of a given length. + pub fn with_default_blocks(mut self, number_of_blocks: u32) -> Self { + self.parentchain = (1..=number_of_blocks) + .map(|n| { + let header = ParentchainHeaderBuilder::default().with_number(n).build(); + ParentchainBlockBuilder::default().with_header(header).build_signed() + }) + .collect(); + self + } +} + +impl ChainApi for ParentchainApiMock { + fn last_finalized_block(&self) -> ApiResult> { + Ok(self.parentchain.last().cloned()) + } + + fn signed_block(&self, _hash: Option) -> ApiResult> { + todo!() + } + + fn get_genesis_hash(&self) -> ApiResult { + todo!() + } + + fn get_header(&self, _header_hash: Option) -> ApiResult> { + todo!() + } + + fn get_blocks(&self, from: u32, to: u32) -> ApiResult> { + let num_elements = to.checked_sub(from).map(|n| n + 1).unwrap_or(0); + let blocks = self + .parentchain + .iter() + .skip(from as usize) + .take(num_elements as usize) + .cloned() + .collect(); + ApiResult::Ok(blocks) + } + + fn is_grandpa_available(&self) -> ApiResult { + todo!() + } + + fn grandpa_authorities(&self, _hash: Option) -> ApiResult { + todo!() + } + + fn grandpa_authorities_proof(&self, _hash: Option) -> ApiResult { + todo!() + } +} diff --git a/tee-worker/service/src/tests/mocks/update_worker_peers_mock.rs b/tee-worker/service/src/tests/mocks/update_worker_peers_mock.rs new file mode 100644 index 0000000000..86c27690fa --- /dev/null +++ b/tee-worker/service/src/tests/mocks/update_worker_peers_mock.rs @@ -0,0 +1,26 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +use crate::{worker::WorkerResult, worker_peers_updater::UpdateWorkerPeers}; + +pub struct UpdateWorkerPeersMock; + +impl UpdateWorkerPeers for UpdateWorkerPeersMock { + fn update_peers(&self) -> WorkerResult<()> { + Ok(()) + } +} diff --git a/tee-worker/service/src/tests/mod.rs b/tee-worker/service/src/tests/mod.rs new file mode 100644 index 0000000000..72cc066bb6 --- /dev/null +++ b/tee-worker/service/src/tests/mod.rs @@ -0,0 +1,44 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +use crate::{config::Config, enclave::api::*, setup}; +use clap::ArgMatches; +use itp_enclave_api::enclave_test::EnclaveTest; + +pub mod commons; +pub mod mock; + +#[cfg(test)] +pub mod mocks; + +#[cfg(test)] +pub mod parentchain_handler_test; + +pub fn run_enclave_tests(matches: &ArgMatches) { + println!("*** Starting Test enclave"); + let config = Config::from(matches); + setup::purge_files_from_cwd().unwrap(); + let enclave = enclave_init(&config).unwrap(); + + if matches.is_present("all") || matches.is_present("unit") { + println!("Running unit Tests"); + enclave.test_main_entrance().unwrap(); + println!("[+] unit_test ended!"); + } + + println!("[+] All tests ended!"); +} diff --git a/tee-worker/service/src/tests/parentchain_handler_test.rs b/tee-worker/service/src/tests/parentchain_handler_test.rs new file mode 100644 index 0000000000..d61f2c8873 --- /dev/null +++ b/tee-worker/service/src/tests/parentchain_handler_test.rs @@ -0,0 +1,49 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +use crate::{ + parentchain_handler::{HandleParentchain, ParentchainHandler}, + tests::mocks::{enclave_api_mock::EnclaveMock, parentchain_api_mock::ParentchainApiMock}, +}; +use itc_parentchain::{ + light_client::light_client_init_params::SimpleParams, primitives::ParentchainInitParams, +}; +use itc_parentchain_test::parentchain_header_builder::ParentchainHeaderBuilder; +use itp_node_api::api_client::ChainApi; +use std::sync::Arc; + +#[test] +fn test_number_of_synced_blocks() { + let number_of_blocks = 42; + + let parentchain_api_mock = ParentchainApiMock::new().with_default_blocks(number_of_blocks); + let last_synced_block = + parentchain_api_mock.get_blocks(2, 2).unwrap().first().cloned().unwrap(); + + let enclave_api_mock = EnclaveMock; + let parentchain_params: ParentchainInitParams = + SimpleParams { genesis_header: ParentchainHeaderBuilder::default().build() }.into(); + + let parentchain_handler = ParentchainHandler::new( + parentchain_api_mock, + Arc::new(enclave_api_mock), + parentchain_params, + ); + + let header = parentchain_handler.sync_parentchain(last_synced_block.block.header).unwrap(); + assert_eq!(header.number, number_of_blocks); +} diff --git a/tee-worker/service/src/utils.rs b/tee-worker/service/src/utils.rs new file mode 100644 index 0000000000..a490cc979d --- /dev/null +++ b/tee-worker/service/src/utils.rs @@ -0,0 +1,51 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + Copyright (C) 2017-2019 Baidu, Inc. All Rights Reserved. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +use base58::{FromBase58, ToBase58}; +use itp_enclave_api::enclave_base::EnclaveBase; +use itp_types::ShardIdentifier; +use log::{debug, info}; +use std::path::Path; + +pub fn extract_shard( + maybe_shard_str: &Option, + enclave_api: &E, +) -> ShardIdentifier { + match maybe_shard_str { + Some(value) => { + let shard_vec = value.from_base58().expect("shard must be hex encoded"); + let mut shard = [0u8; 32]; + shard.copy_from_slice(&shard_vec[..]); + shard.into() + }, + _ => { + let mrenclave = enclave_api.get_mrenclave().unwrap(); + info!("no shard specified. using mrenclave as id: {}", mrenclave.to_base58()); + ShardIdentifier::from_slice(&mrenclave[..]) + }, + } +} + +pub fn check_files() { + use itp_settings::files::{ENCLAVE_FILE, RA_API_KEY_FILE, RA_SPID_FILE}; + debug!("*** Check files"); + let files = vec![ENCLAVE_FILE, RA_SPID_FILE, RA_API_KEY_FILE]; + for f in files.iter() { + assert!(Path::new(f).exists(), "File doesn't exist: {}", f); + } +} diff --git a/tee-worker/service/src/wasm.rs b/tee-worker/service/src/wasm.rs new file mode 100644 index 0000000000..d07fb8c06c --- /dev/null +++ b/tee-worker/service/src/wasm.rs @@ -0,0 +1,57 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +use sgx_types::*; + +extern "C" { + fn sgxwasm_init(eid: sgx_enclave_id_t, retval: *mut sgx_status_t) -> sgx_status_t; +} + +#[derive(Debug, Serialize, Deserialize)] +pub enum SgxWasmAction { + Call { module: Option>, function: String }, +} + +#[derive(Debug, Serialize, Deserialize)] +pub enum BoundaryValue { + I32(i32), + I64(i64), + F32(u32), + F64(u64), +} + +pub fn sgx_enclave_wasm_init(eid: sgx_enclave_id_t) -> Result<(), String> { + let mut retval: sgx_status_t = sgx_status_t::SGX_SUCCESS; + let result = unsafe { sgxwasm_init(eid, &mut retval) }; + + match result { + sgx_status_t::SGX_SUCCESS => {}, + _ => { + println!("[-] ECALL Enclave Failed {}!", result.as_str()); + panic!("sgx_enclave_wasm_init's ECALL returned unknown error!"); + }, + } + + match retval { + sgx_status_t::SGX_SUCCESS => {}, + _ => { + println!("[-] ECALL Enclave Function return fail: {}!", retval.as_str()); + return Err(format!("ECALL func return error: {}", retval.as_str())) + }, + } + + Ok(()) +} diff --git a/tee-worker/service/src/worker.rs b/tee-worker/service/src/worker.rs new file mode 100644 index 0000000000..d866528fa1 --- /dev/null +++ b/tee-worker/service/src/worker.rs @@ -0,0 +1,241 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +///! Integritee worker. Inspiration for this design came from parity's substrate Client. +/// +/// This should serve as a proof of concept for a potential refactoring design. Ultimately, everything +/// from the main.rs should be covered by the worker struct here - hidden and split across +/// multiple traits. +use crate::{config::Config, error::Error, TrackInitialization}; +use async_trait::async_trait; +use itc_rpc_client::direct_client::{DirectApi, DirectClient as DirectWorkerApi}; +use itp_node_api::{api_client::PalletTeerexApi, node_api_factory::CreateNodeApi}; +use its_primitives::types::SignedBlock as SignedSidechainBlock; +use its_rpc_handler::constants::RPC_METHOD_NAME_IMPORT_BLOCKS; +use jsonrpsee::{ + types::{to_json_value, traits::Client}, + ws_client::WsClientBuilder, +}; +use log::*; +use std::sync::{Arc, RwLock}; + +pub type WorkerResult = Result; +pub type Url = String; +pub struct Worker { + _config: Config, + // unused yet, but will be used when more methods are migrated to the worker + _enclave_api: Arc, + node_api_factory: Arc, + initialization_handler: Arc, + peers: RwLock>, +} + +impl + Worker +{ + pub fn new( + config: Config, + enclave_api: Arc, + node_api_factory: Arc, + initialization_handler: Arc, + peers: Vec, + ) -> Self { + Self { + _config: config, + _enclave_api: enclave_api, + node_api_factory, + initialization_handler, + peers: RwLock::new(peers), + } + } +} + +#[async_trait] +/// Broadcast Sidechain blocks to peers. +pub trait AsyncBlockBroadcaster { + async fn broadcast_blocks(&self, blocks: Vec) -> WorkerResult<()>; +} + +#[async_trait] +impl AsyncBlockBroadcaster + for Worker +where + NodeApiFactory: CreateNodeApi + Send + Sync, + Enclave: Send + Sync, + InitializationHandler: TrackInitialization + Send + Sync, +{ + async fn broadcast_blocks(&self, blocks: Vec) -> WorkerResult<()> { + if blocks.is_empty() { + debug!("No blocks to broadcast, returning"); + return Ok(()) + } + + let blocks_json = vec![to_json_value(blocks)?]; + let peers = self + .peers + .read() + .map_err(|e| { + Error::Custom(format!("Encountered poisoned lock for peers: {:?}", e).into()) + }) + .map(|l| l.clone())?; + + self.initialization_handler.sidechain_block_produced(); + + for url in peers { + let blocks = blocks_json.clone(); + + tokio::spawn(async move { + debug!("Broadcasting block to peer with address: {:?}", url); + // FIXME: Websocket connection to a worker should stay, once established. + let client = match WsClientBuilder::default().build(&url).await { + Ok(c) => c, + Err(e) => { + error!("Failed to create websocket client for block broadcasting (target url: {}): {:?}", url, e); + return + }, + }; + + if let Err(e) = + client.request::>(RPC_METHOD_NAME_IMPORT_BLOCKS, blocks.into()).await + { + error!( + "Broadcast block request ({}) to {} failed: {:?}", + RPC_METHOD_NAME_IMPORT_BLOCKS, url, e + ); + } + }); + } + Ok(()) + } +} + +/// Looks for new peers and updates them. +pub trait UpdatePeers { + fn search_peers(&self) -> WorkerResult>; + + fn set_peers(&self, peers: Vec) -> WorkerResult<()>; + + fn update_peers(&self) -> WorkerResult<()> { + let peers = self.search_peers()?; + self.set_peers(peers) + } +} + +impl UpdatePeers + for Worker +where + NodeApiFactory: CreateNodeApi + Send + Sync, +{ + fn search_peers(&self) -> WorkerResult> { + let node_api = self + .node_api_factory + .create_api() + .map_err(|e| Error::Custom(format!("Failed to create NodeApi: {:?}", e).into()))?; + let enclaves = node_api.all_enclaves(None)?; + let mut peer_urls = Vec::::new(); + for enclave in enclaves { + // FIXME: This is temporary only, as block broadcasting should be moved to trusted ws server. + let enclave_url = enclave.url.clone(); + let worker_api_direct = DirectWorkerApi::new(enclave.url); + let untrusted_worker_url = + worker_api_direct.get_untrusted_worker_url().map_err(|e| { + error!( + "Failed to get untrusted worker url (enclave: {}): {:?}", + enclave_url, e + ); + e + })?; + peer_urls.push(untrusted_worker_url); + } + Ok(peer_urls) + } + + fn set_peers(&self, peers: Vec) -> WorkerResult<()> { + let mut peers_lock = self.peers.write().map_err(|e| { + Error::Custom(format!("Encountered poisoned lock for peers: {:?}", e).into()) + })?; + *peers_lock = peers; + Ok(()) + } +} +#[cfg(test)] +mod tests { + use super::*; + use crate::{ + tests::{ + commons::local_worker_config, + mock::{W1_URL, W2_URL}, + mocks::initialization_handler_mock::TrackInitializationMock, + }, + worker::{AsyncBlockBroadcaster, Worker}, + }; + use frame_support::assert_ok; + use itp_node_api::node_api_factory::NodeApiFactory; + use its_primitives::types::block::SignedBlock as SignedSidechainBlock; + use its_test::sidechain_block_builder::SidechainBlockBuilder; + use jsonrpsee::{ws_server::WsServerBuilder, RpcModule}; + use log::debug; + use sp_keyring::AccountKeyring; + use std::{net::SocketAddr, sync::Arc}; + use tokio::net::ToSocketAddrs; + + fn init() { + let _ = env_logger::builder().is_test(true).try_init(); + } + + async fn run_server(addr: impl ToSocketAddrs) -> anyhow::Result { + let mut server = WsServerBuilder::default().build(addr).await?; + let mut module = RpcModule::new(()); + + module.register_method(RPC_METHOD_NAME_IMPORT_BLOCKS, |params, _| { + debug!("{} params: {:?}", RPC_METHOD_NAME_IMPORT_BLOCKS, params); + let _blocks: Vec = params.one()?; + Ok("ok".as_bytes().to_vec()) + })?; + + server.register_module(module).unwrap(); + + let socket_addr = server.local_addr()?; + tokio::spawn(async move { server.start().await }); + Ok(socket_addr) + } + + #[tokio::test] + async fn broadcast_blocks_works() { + init(); + run_server(W1_URL).await.unwrap(); + run_server(W2_URL).await.unwrap(); + let untrusted_worker_port = "4000".to_string(); + let peers = vec![format!("ws://{}", W1_URL), format!("ws://{}", W2_URL)]; + + let worker = Worker::new( + local_worker_config(W1_URL.into(), untrusted_worker_port.clone(), "30".to_string()), + Arc::new(()), + Arc::new(NodeApiFactory::new( + "ws://invalid.url".to_string(), + AccountKeyring::Alice.pair(), + )), + Arc::new(TrackInitializationMock {}), + peers, + ); + + let resp = worker + .broadcast_blocks(vec![SidechainBlockBuilder::default().build_signed()]) + .await; + assert_ok!(resp); + } +} diff --git a/tee-worker/service/src/worker_peers_updater.rs b/tee-worker/service/src/worker_peers_updater.rs new file mode 100644 index 0000000000..5b536ef667 --- /dev/null +++ b/tee-worker/service/src/worker_peers_updater.rs @@ -0,0 +1,50 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + Copyright (C) 2017-2019 Baidu, Inc. All Rights Reserved. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +#[cfg(test)] +use mockall::predicate::*; +#[cfg(test)] +use mockall::*; + +use crate::worker::{UpdatePeers, WorkerResult}; +use std::sync::Arc; + +/// Updates the peers of the global worker. +#[cfg_attr(test, automock)] +pub trait UpdateWorkerPeers { + fn update_peers(&self) -> WorkerResult<()>; +} + +pub struct WorkerPeersUpdater { + worker: Arc, +} + +impl WorkerPeersUpdater { + pub fn new(worker: Arc) -> Self { + WorkerPeersUpdater { worker } + } +} + +impl UpdateWorkerPeers for WorkerPeersUpdater +where + WorkerType: UpdatePeers, +{ + fn update_peers(&self) -> WorkerResult<()> { + self.worker.update_peers() + } +} diff --git a/tee-worker/sidechain/block-composer/Cargo.toml b/tee-worker/sidechain/block-composer/Cargo.toml new file mode 100644 index 0000000000..66da58d8b9 --- /dev/null +++ b/tee-worker/sidechain/block-composer/Cargo.toml @@ -0,0 +1,67 @@ +[package] +authors = ["Integritee AG "] +edition = "2021" +name = "its-block-composer" +version = "0.9.0" + +[dependencies] +# sgx dependencies +sgx_tstd = { branch = "master", git = "https://github.com/apache/teaclave-sgx-sdk.git", optional = true } +sgx_types = { branch = "master", git = "https://github.com/apache/teaclave-sgx-sdk.git" } + +# local dependencies +ita-stf = { path = "../../app-libs/stf", default-features = false } +itp-node-api = { path = "../../core-primitives/node-api", default-features = false } +itp-settings = { path = "../../core-primitives/settings", default-features = false } +itp-sgx-crypto = { path = "../../core-primitives/sgx/crypto", default-features = false } +itp-sgx-externalities = { path = "../../core-primitives/substrate-sgx/externalities", default-features = false } +itp-stf-executor = { path = "../../core-primitives/stf-executor", default-features = false } +itp-stf-interface = { path = "../../core-primitives/stf-interface", default-features = false } +itp-time-utils = { path = "../../core-primitives/time-utils", default-features = false } +itp-top-pool-author = { path = "../../core-primitives/top-pool-author", default-features = false } +itp-types = { path = "../../core-primitives/types", default-features = false } +its-primitives = { path = "../primitives", default-features = false, features = ["full_crypto"] } +its-state = { path = "../state", default-features = false } + +# sgx enabled external libraries +thiserror_sgx = { package = "thiserror", git = "https://github.com/mesalock-linux/thiserror-sgx", tag = "sgx_1.1.3", optional = true } + +# std compatible external libraries (make sure these versions match with the sgx-enabled ones above) +thiserror = { version = "1.0", optional = true } + +# no-std compatible libraries +codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } +log = { version = "0.4", default-features = false } +sp-core = { default-features = false, features = ["full_crypto"], git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.29" } +sp-runtime = { default-features = false, git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.29" } + + +[features] +default = ["std"] +sgx = [ + "sgx_tstd", + "ita-stf/sgx", + "itp-node-api/sgx", + "itp-sgx-crypto/sgx", + "itp-sgx-externalities/sgx", + "itp-stf-executor/sgx", + "itp-time-utils/sgx", + "itp-top-pool-author/sgx", + "its-state/sgx", + "thiserror_sgx", +] +std = [ + "ita-stf/std", + "itp-node-api/std", + "itp-sgx-crypto/std", + "itp-sgx-externalities/std", + "itp-stf-executor/std", + "itp-stf-interface/std", + "itp-time-utils/std", + "itp-top-pool-author/std", + "itp-types/std", + "its-primitives/std", + "its-state/std", + "log/std", + "thiserror", +] diff --git a/tee-worker/sidechain/block-composer/src/block_composer.rs b/tee-worker/sidechain/block-composer/src/block_composer.rs new file mode 100644 index 0000000000..a7f5a2c8e0 --- /dev/null +++ b/tee-worker/sidechain/block-composer/src/block_composer.rs @@ -0,0 +1,180 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +use crate::error::{Error, Result}; +use codec::Encode; +use ita_stf::StatePayload; +use itp_settings::worker::BLOCK_NUMBER_FINALIZATION_DIFF; +use itp_sgx_crypto::{key_repository::AccessKey, StateCrypto}; +use itp_sgx_externalities::{SgxExternalitiesTrait, StateHash}; +use itp_time_utils::now_as_u64; +use itp_types::{ShardIdentifier, H256}; +use its_primitives::traits::{ + Block as SidechainBlockTrait, BlockData, Header as HeaderTrait, SignBlock, + SignedBlock as SignedSidechainBlockTrait, +}; +use its_state::{LastBlockExt, SidechainDB, SidechainState, SidechainSystemExt}; +use log::*; +use sp_core::Pair; +use sp_runtime::{ + traits::{Block as ParentchainBlockTrait, Header}, + MultiSignature, +}; +use std::{format, marker::PhantomData, sync::Arc, vec::Vec}; + +/// Compose a sidechain block and corresponding confirmation extrinsic for the parentchain +/// +pub trait ComposeBlock { + type SignedSidechainBlock: SignedSidechainBlockTrait; + + fn compose_block( + &self, + latest_parentchain_header: &::Header, + top_call_hashes: Vec, + shard: ShardIdentifier, + state_hash_apriori: H256, + aposteriori_state: Externalities, + ) -> Result; +} + +/// Block composer implementation for the sidechain +pub struct BlockComposer { + signer: Signer, + state_key_repository: Arc, + _phantom: PhantomData<(ParentchainBlock, SignedSidechainBlock)>, +} + +impl + BlockComposer +where + ParentchainBlock: ParentchainBlockTrait, + SignedSidechainBlock: + SignedSidechainBlockTrait, + SignedSidechainBlock::Block: SidechainBlockTrait, + <::Block as SidechainBlockTrait>::HeaderType: + HeaderTrait, + SignedSidechainBlock::Signature: From, + Signer: Pair, + Signer::Public: Encode, + StateKeyRepository: AccessKey, + ::KeyType: StateCrypto, +{ + pub fn new(signer: Signer, state_key_repository: Arc) -> Self { + BlockComposer { signer, state_key_repository, _phantom: Default::default() } + } +} + +type HeaderTypeOf = <::Block as SidechainBlockTrait>::HeaderType; +type BlockDataTypeOf = + <::Block as SidechainBlockTrait>::BlockDataType; + +impl + ComposeBlock + for BlockComposer +where + ParentchainBlock: ParentchainBlockTrait, + SignedSidechainBlock: + SignedSidechainBlockTrait, + SignedSidechainBlock::Block: SidechainBlockTrait, + <::Block as SidechainBlockTrait>::HeaderType: + HeaderTrait, + SignedSidechainBlock::Signature: From, + Externalities: SgxExternalitiesTrait + SidechainState + SidechainSystemExt + StateHash + Encode, + ::SgxExternalitiesType: Encode, + ::SgxExternalitiesDiffType: Encode, + Signer: Pair, + Signer::Public: Encode, + StateKeyRepository: AccessKey, + ::KeyType: StateCrypto, +{ + type SignedSidechainBlock = SignedSidechainBlock; + + fn compose_block( + &self, + latest_parentchain_header: &ParentchainBlock::Header, + top_call_hashes: Vec, + shard: ShardIdentifier, + state_hash_apriori: H256, + aposteriori_state: Externalities, + ) -> Result { + let author_public = self.signer.public(); + + let state_hash_new = aposteriori_state.hash(); + let db = SidechainDB::::new(aposteriori_state); + + let (block_number, parent_hash, next_finalization_block_number) = match db.get_last_block() + { + Some(block) => ( + block.header().block_number() + 1, + block.hash(), + block.header().next_finalization_block_number(), + ), + None => { + info!("Seems to be first sidechain block."); + (1, Default::default(), 1) + }, + }; + + if block_number != db.get_block_number().unwrap_or(0) { + return Err(Error::Other("[Sidechain] BlockNumber is not LastBlock's Number + 1".into())) + } + + // create encrypted payload + let mut payload: Vec = + StatePayload::new(state_hash_apriori, state_hash_new, db.ext().state_diff()).encode(); + + let state_key = self + .state_key_repository + .retrieve_key() + .map_err(|e| Error::Other(format!("Failed to retrieve state key: {:?}", e).into()))?; + + state_key.encrypt(&mut payload).map_err(|e| { + Error::Other(format!("Failed to encrypt state payload: {:?}", e).into()) + })?; + + let block_data = BlockDataTypeOf::::new( + author_public, + latest_parentchain_header.hash(), + top_call_hashes, + payload, + now_as_u64(), + ); + + let mut finalization_candidate = next_finalization_block_number; + if block_number == 1 { + finalization_candidate = 1; + } else if block_number > finalization_candidate { + finalization_candidate += BLOCK_NUMBER_FINALIZATION_DIFF; + } + + let header = HeaderTypeOf::::new( + block_number, + parent_hash, + shard, + block_data.hash(), + finalization_candidate, + ); + + let block = SignedSidechainBlock::Block::new(header.clone(), block_data); + + debug!("Block header hash {}", header.hash()); + + let signed_block = block.sign_block(&self.signer); + + Ok(signed_block) + } +} diff --git a/tee-worker/sidechain/block-composer/src/error.rs b/tee-worker/sidechain/block-composer/src/error.rs new file mode 100644 index 0000000000..6baba32eb7 --- /dev/null +++ b/tee-worker/sidechain/block-composer/src/error.rs @@ -0,0 +1,59 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +#[cfg(all(not(feature = "std"), feature = "sgx"))] +use crate::sgx_reexport_prelude::*; + +use sgx_types::sgx_status_t; +use std::{boxed::Box, format}; + +pub type Result = core::result::Result; + +/// Block composer error +#[derive(Debug, thiserror::Error)] +pub enum Error { + #[error("SGX error, status: {0}")] + Sgx(sgx_status_t), + #[error("STF execution error: {0}")] + StfExecution(#[from] itp_stf_executor::error::Error), + #[error("TOP pool RPC author error: {0}")] + TopPoolAuthor(#[from] itp_top_pool_author::error::Error), + #[error("Node Metadata error: {0:?}")] + NodeMetadata(itp_node_api::metadata::Error), + #[error("Node metadata provider error: {0:?}")] + NodeMetadataProvider(#[from] itp_node_api::metadata::provider::Error), + #[error(transparent)] + Other(#[from] Box), +} + +impl From for Error { + fn from(sgx_status: sgx_status_t) -> Self { + Self::Sgx(sgx_status) + } +} + +impl From for Error { + fn from(e: codec::Error) -> Self { + Self::Other(format!("{:?}", e).into()) + } +} + +impl From for Error { + fn from(e: itp_node_api::metadata::Error) -> Self { + Self::NodeMetadata(e) + } +} diff --git a/tee-worker/sidechain/block-composer/src/lib.rs b/tee-worker/sidechain/block-composer/src/lib.rs new file mode 100644 index 0000000000..038f348c1d --- /dev/null +++ b/tee-worker/sidechain/block-composer/src/lib.rs @@ -0,0 +1,36 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ +//! Sidechain block composing logic. +#![feature(trait_alias)] +#![cfg_attr(not(feature = "std"), no_std)] + +#[cfg(all(feature = "std", feature = "sgx"))] +compile_error!("feature \"std\" and feature \"sgx\" cannot be enabled at the same time"); + +#[cfg(all(not(feature = "std"), feature = "sgx"))] +extern crate sgx_tstd as std; + +// re-export module to properly feature gate sgx and regular std environment +#[cfg(all(not(feature = "std"), feature = "sgx"))] +pub mod sgx_reexport_prelude { + pub use thiserror_sgx as thiserror; +} + +pub mod block_composer; +pub mod error; + +pub use block_composer::*; diff --git a/tee-worker/sidechain/block-verification/Cargo.toml b/tee-worker/sidechain/block-verification/Cargo.toml new file mode 100644 index 0000000000..09905c236c --- /dev/null +++ b/tee-worker/sidechain/block-verification/Cargo.toml @@ -0,0 +1,54 @@ +[package] +authors = ["Integritee AG "] +description = "Verification logic for sidechain blocks" +edition = "2021" +homepage = "https://integritee.network/" +license = "Apache-2.0" +name = "its-block-verification" +repository = "https://github.com/integritee-network/pallets/" +version = "0.9.0" + +[dependencies] +log = { version = "0.4.17", default-features = false } +thiserror = { version = "1.0.26", optional = true } + +# local deps +itp-types = { default-features = false, path = "../../core-primitives/types" } +itp-utils = { default-features = false, path = "../../core-primitives/utils" } +its-primitives = { default-features = false, path = "../primitives" } + +# substrate deps +frame-support = { default-features = false, git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.29" } +sp-consensus-slots = { default-features = false, git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.29" } +sp-core = { default-features = false, git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.29" } +sp-runtime = { default-features = false, git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.29" } + +# sgx deps +sgx_tstd = { branch = "master", features = ["untrusted_fs", "net", "backtrace"], git = "https://github.com/apache/teaclave-sgx-sdk.git", optional = true } +thiserror-sgx = { package = "thiserror", git = "https://github.com/mesalock-linux/thiserror-sgx", tag = "sgx_1.1.3", optional = true } + +[features] +default = ["std"] +sgx = [ + "itp-utils/sgx", + "sgx_tstd", + "thiserror-sgx", +] +std = [ + "log/std", + "thiserror", + # local + "itp-types/std", + "itp-utils/std", + "its-primitives/std", + # substrate + "frame-support/std", + "sp-consensus-slots/std", + "sp-core/std", + "sp-runtime/std", +] + +[dev-dependencies] +itc-parentchain-test = { path = "../../core/parentchain/test" } +its-test = { path = "../../sidechain/test" } +sp-keyring = { git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.29" } diff --git a/tee-worker/sidechain/block-verification/src/error.rs b/tee-worker/sidechain/block-verification/src/error.rs new file mode 100644 index 0000000000..bac9b8d60b --- /dev/null +++ b/tee-worker/sidechain/block-verification/src/error.rs @@ -0,0 +1,46 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +//! Error types in sidechain consensus + +use itp_types::BlockHash as ParentchainBlockHash; +use its_primitives::types::{block::BlockHash as SidechainBlockHash, BlockNumber}; +use std::string::String; + +pub type Result = std::result::Result; + +#[cfg(all(not(feature = "std"), feature = "sgx"))] +pub use thiserror_sgx as thiserror; + +#[derive(Debug, thiserror::Error)] +#[non_exhaustive] +pub enum Error { + #[error("Message sender {0} is not a valid authority")] + InvalidAuthority(String), + #[error("Could not get authorities: {0:?}.")] + CouldNotGetAuthorities(String), + #[error("Bad parentchain block (Hash={0}). Reason: {1}")] + BadParentchainBlock(ParentchainBlockHash, String), + #[error("Bad sidechain block (Hash={0}). Reason: {1}")] + BadSidechainBlock(SidechainBlockHash, String), + #[error("Could not import new block due to {2}. (Last imported by number: {0:?})")] + BlockAncestryMismatch(BlockNumber, SidechainBlockHash, String), + #[error("Could not import new block. Expected first block, but found {0}. {1:?}")] + InvalidFirstBlock(BlockNumber, String), + #[error("Could not import block (number: {0}). A block with this number is already imported (current state block number: {1})")] + BlockAlreadyImported(BlockNumber, BlockNumber), +} diff --git a/tee-worker/sidechain/block-verification/src/lib.rs b/tee-worker/sidechain/block-verification/src/lib.rs new file mode 100644 index 0000000000..662e693233 --- /dev/null +++ b/tee-worker/sidechain/block-verification/src/lib.rs @@ -0,0 +1,491 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ +#![feature(assert_matches)] +#![cfg_attr(not(feature = "std"), no_std)] + +#[cfg(all(feature = "std", feature = "sgx"))] +compile_error!("feature \"std\" and feature \"sgx\" cannot be enabled at the same time"); + +#[cfg(all(not(feature = "std"), not(feature = "sgx")))] +compile_error!("feature \"std\" and feature \"sgx\" cannot be disabled at the same time"); + +#[cfg(all(not(feature = "std"), feature = "sgx"))] +#[macro_use] +extern crate sgx_tstd as std; + +use crate::slot::{slot_author, slot_from_timestamp_and_duration}; +use error::Error as ConsensusError; +use frame_support::ensure; +use itp_utils::stringify::public_to_string; +use its_primitives::{ + traits::{ + Block as SidechainBlockTrait, BlockData, Header as HeaderTrait, + SignedBlock as SignedSidechainBlockTrait, SignedBlock, + }, + types::block::BlockHash, +}; +use log::*; +pub use sp_consensus_slots::Slot; +use sp_runtime::{ + app_crypto::Pair, + traits::{Block as ParentchainBlockTrait, Header as ParentchainHeaderTrait}, +}; +use std::{fmt::Debug, time::Duration}; + +pub mod error; +pub mod slot; + +type AuthorityId

=

::Public; + +pub fn verify_sidechain_block( + signed_block: SignedSidechainBlock, + slot_duration: Duration, + last_block: &Option<::Block>, + parentchain_header: &ParentchainBlock::Header, + authorities: &[AuthorityId], +) -> Result +where + AuthorityPair: Pair, + AuthorityPair::Public: Debug, + ParentchainBlock: ParentchainBlockTrait, + SignedSidechainBlock: 'static + SignedSidechainBlockTrait, + SignedSidechainBlock::Block: SidechainBlockTrait, +{ + ensure!( + signed_block.verify_signature(), + ConsensusError::BadSidechainBlock(signed_block.block().hash(), "bad signature".into()) + ); + + let slot = slot_from_timestamp_and_duration( + Duration::from_millis(signed_block.block().block_data().timestamp()), + slot_duration, + ); + + // We need to check the ancestry first to ensure that an already imported block does not result + // in an author verification error, but rather a `BlockAlreadyImported` error. + match last_block { + Some(last_block) => + verify_block_ancestry::(signed_block.block(), last_block)?, + None => ensure_first_block(signed_block.block())?, + } + + if let Err(e) = verify_author::( + &slot, + signed_block.block(), + parentchain_header, + authorities, + ) { + error!( + "Author verification for block (number: {}) failed, block will be discarded", + signed_block.block().header().block_number() + ); + return Err(e) + } + + Ok(signed_block) +} + +/// Verify that the `blocks` author is the expected author when comparing with onchain data. +fn verify_author( + slot: &Slot, + block: &SignedSidechainBlock::Block, + parentchain_head: &ParentchainHeader, + authorities: &[AuthorityId], +) -> Result<(), ConsensusError> +where + AuthorityPair: Pair, + AuthorityPair::Public: Debug, + SignedSidechainBlock: SignedSidechainBlockTrait + 'static, + ParentchainHeader: ParentchainHeaderTrait, +{ + ensure!( + parentchain_head.hash() == block.block_data().layer_one_head(), + ConsensusError::BadParentchainBlock( + parentchain_head.hash(), + "Invalid parentchain head".into(), + ) + ); + + let expected_author = slot_author::(*slot, authorities) + .ok_or_else(|| ConsensusError::CouldNotGetAuthorities("No authorities found".into()))?; + + ensure!( + expected_author == block.block_data().block_author(), + ConsensusError::InvalidAuthority(format!( + "Expected author: {}, author found in block: {}", + public_to_string(expected_author), + public_to_string(block.block_data().block_author()) + )) + ); + + Ok(()) +} + +fn verify_block_ancestry( + block: &SidechainBlock, + last_block: &SidechainBlock, +) -> Result<(), ConsensusError> { + // These next two checks might seem redundant at first glance. However, they are distinct (see comments). + + // We have already imported this block. + ensure!( + block.header().block_number() > last_block.header().block_number(), + ConsensusError::BlockAlreadyImported( + block.header().block_number(), + last_block.header().block_number() + ) + ); + + // We are missing some blocks between our last known block and the one we're trying to import. + ensure!( + last_block.header().block_number() + 1 == block.header().block_number(), + ConsensusError::BlockAncestryMismatch( + last_block.header().block_number(), + last_block.hash(), + format!( + "Invalid block number, {} does not succeed {}", + block.header().block_number(), + last_block.header().block_number() + ) + ) + ); + + ensure!( + last_block.hash() == block.header().parent_hash(), + ConsensusError::BlockAncestryMismatch( + last_block.header().block_number(), + last_block.hash(), + "Parent hash does not match".into(), + ) + ); + + Ok(()) +} + +fn ensure_first_block( + block: &SidechainBlock, +) -> Result<(), ConsensusError> { + ensure!( + block.header().block_number() == 1, + ConsensusError::InvalidFirstBlock( + block.header().block_number(), + "No last block found, expecting first block. But block to import has number != 1" + .into() + ) + ); + ensure!( + block.header().parent_hash() == Default::default(), + ConsensusError::InvalidFirstBlock( + block.header().block_number(), + "No last block found, excepting first block. But block to import has parent_hash != 0" + .into() + ) + ); + + Ok(()) +} + +#[cfg(test)] +mod tests { + use super::*; + use core::assert_matches::assert_matches; + use frame_support::assert_ok; + use itc_parentchain_test::parentchain_header_builder::ParentchainHeaderBuilder; + use itp_types::{AccountId, Block as ParentchainBlock}; + use its_primitives::types::{block::SignedBlock, header::SidechainHeader as Header}; + use its_test::{ + sidechain_block_builder::SidechainBlockBuilder, + sidechain_block_data_builder::SidechainBlockDataBuilder, + sidechain_header_builder::SidechainHeaderBuilder, + }; + use sp_core::{ed25519::Pair, ByteArray, H256}; + use sp_keyring::ed25519::Keyring; + + pub const SLOT_DURATION: Duration = Duration::from_millis(300); + + fn assert_ancestry_mismatch_err(result: Result) { + assert_matches!(result, Err(ConsensusError::BlockAncestryMismatch(_, _, _,))) + } + + fn block(signer: Keyring, header: Header) -> SignedBlock { + let parentchain_header = ParentchainHeaderBuilder::default().build(); + let block_data = SidechainBlockDataBuilder::default() + .with_signer(signer.pair()) + .with_timestamp(0) + .with_layer_one_head(parentchain_header.hash()) + .build(); + + SidechainBlockBuilder::default() + .with_header(header) + .with_block_data(block_data) + .with_signer(signer.pair()) + .build_signed() + } + + fn block1(signer: Keyring) -> SignedBlock { + let header = SidechainHeaderBuilder::default().with_block_number(1).build(); + + block(signer, header) + } + + fn block2(signer: Keyring, parent_hash: H256) -> SignedBlock { + let header = SidechainHeaderBuilder::default() + .with_parent_hash(parent_hash) + .with_block_number(2) + .build(); + + block(signer, header) + } + + fn block3(signer: Keyring, parent_hash: H256, block_number: u64) -> SignedBlock { + let header = SidechainHeaderBuilder::default() + .with_parent_hash(parent_hash) + .with_block_number(block_number) + .build(); + + block(signer, header) + } + + #[test] + fn ensure_first_block_works() { + let block = SidechainBlockBuilder::default().build(); + assert_ok!(ensure_first_block(&block)); + } + + #[test] + fn ensure_first_block_errs_with_invalid_block_number() { + let header = SidechainHeaderBuilder::default().with_block_number(2).build(); + let block = SidechainBlockBuilder::default().with_header(header).build(); + assert_matches!(ensure_first_block(&block), Err(ConsensusError::InvalidFirstBlock(2, _))) + } + + #[test] + fn ensure_first_block_errs_with_invalid_parent_hash() { + let parent = H256::random(); + let header = SidechainHeaderBuilder::default().with_parent_hash(parent).build(); + let block = SidechainBlockBuilder::default().with_header(header).build(); + + assert_matches!(ensure_first_block(&block), Err(ConsensusError::InvalidFirstBlock(_, _))); + } + + #[test] + fn verify_block_ancestry_works() { + let last_block = SidechainBlockBuilder::default().build(); + let header = SidechainHeaderBuilder::default() + .with_parent_hash(last_block.hash()) + .with_block_number(2) + .build(); + let curr_block = SidechainBlockBuilder::default().with_header(header).build(); + + assert_ok!(verify_block_ancestry(&curr_block, &last_block)); + } + + #[test] + fn verify_block_ancestry_errs_with_invalid_parent_block_number() { + let last_block = SidechainBlockBuilder::default().build(); + let header = SidechainHeaderBuilder::default() + .with_parent_hash(last_block.hash()) + .with_block_number(5) + .build(); + let curr_block = SidechainBlockBuilder::default().with_header(header).build(); + + assert_ancestry_mismatch_err(verify_block_ancestry(&curr_block, &last_block)); + } + + #[test] + fn verify_block_ancestry_errs_with_invalid_parent_hash() { + let last_block = SidechainBlockBuilder::default().build(); + let header = SidechainHeaderBuilder::default().with_block_number(2).build(); + let curr_block = SidechainBlockBuilder::default().with_header(header).build(); + + assert_ancestry_mismatch_err(verify_block_ancestry(&curr_block, &last_block)); + } + + #[test] + fn verify_works() { + let signer = Keyring::Alice; + let signer_account: AccountId = signer.public().into(); + let authorities = [AuthorityId::::from_slice(signer_account.as_ref()).unwrap()]; + + let parentchain_header = ParentchainHeaderBuilder::default().build(); + let last_block = SidechainBlockBuilder::default().build(); + let curr_block = block2(signer, last_block.hash()); + + assert_ok!(verify_sidechain_block::( + curr_block, + SLOT_DURATION, + &Some(last_block), + &parentchain_header, + &authorities, + )); + } + + #[test] + fn verify_works_for_first_block() { + let signer = Keyring::Alice; + let signer_account: AccountId = signer.public().into(); + let authorities = [AuthorityId::::from_slice(signer_account.as_ref()).unwrap()]; + + let parentchain_header = ParentchainHeaderBuilder::default().build(); + let curr_block = block1(signer); + + assert_ok!(verify_sidechain_block::( + curr_block, + SLOT_DURATION, + &None, + &parentchain_header, + &authorities, + )); + } + + #[test] + fn verify_errs_on_wrong_authority() { + let signer = Keyring::Alice; + let signer_account: AccountId = signer.public().into(); + let bob_account: AccountId = Keyring::Bob.public().into(); + let authorities = [ + AuthorityId::::from_slice(bob_account.as_ref()).unwrap(), + AuthorityId::::from_slice(signer_account.as_ref()).unwrap(), + ]; + + let parentchain_header = ParentchainHeaderBuilder::default().build(); + let last_block = SidechainBlockBuilder::default().build(); + let curr_block = block2(signer, last_block.hash()); + + assert_matches!( + verify_sidechain_block::( + curr_block, + SLOT_DURATION, + &Some(last_block), + &parentchain_header, + &authorities, + ) + .unwrap_err(), + ConsensusError::InvalidAuthority(_) + ); + } + + #[test] + fn verify_errs_on_invalid_ancestry() { + let signer = Keyring::Alice; + let signer_account: AccountId = signer.public().into(); + let authorities = [AuthorityId::::from_slice(signer_account.as_ref()).unwrap()]; + + let parentchain_header = ParentchainHeaderBuilder::default().build(); + let last_block = SidechainBlockBuilder::default().build(); + let curr_block = block2(signer, Default::default()); + + assert_ancestry_mismatch_err(verify_sidechain_block::( + curr_block, + SLOT_DURATION, + &Some(last_block), + &parentchain_header, + &authorities, + )); + } + + #[test] + fn verify_errs_on_wrong_first_block() { + let signer = Keyring::Alice; + let signer_account: AccountId = signer.public().into(); + let authorities = [AuthorityId::::from_slice(signer_account.as_ref()).unwrap()]; + + let parentchain_header = ParentchainHeaderBuilder::default().build(); + let curr_block = block2(signer, Default::default()); + + assert_matches!( + verify_sidechain_block::( + curr_block, + SLOT_DURATION, + &None, + &parentchain_header, + &authorities, + ) + .unwrap_err(), + ConsensusError::InvalidFirstBlock(2, _) + ); + } + + #[test] + fn verify_errs_on_already_imported_block() { + let signer = Keyring::Alice; + let signer_account: AccountId = signer.public().into(); + let authorities = [AuthorityId::::from_slice(signer_account.as_ref()).unwrap()]; + + let parentchain_header = ParentchainHeaderBuilder::default().build(); + let last_block = SidechainBlockBuilder::default().build(); + // Current block has also number 1, same as last. So import should return an error + // that a block with this number is already imported. + let curr_block = block3(signer, last_block.hash(), 1); + + assert_matches!( + verify_sidechain_block::( + curr_block, + SLOT_DURATION, + &Some(last_block), + &parentchain_header, + &authorities, + ) + .unwrap_err(), + ConsensusError::BlockAlreadyImported(1, 1) + ); + } + + #[test] + fn verify_block_already_imported_error_even_if_parentchain_block_mismatches() { + // This test is to ensure that we get a 'AlreadyImported' error, when the sidechain block + // is already imported, and the parentchain block that is passed into the verifier is newer. + // Important because client of the verifier acts differently for an 'AlreadyImported' error than an 'AncestryErrorMismatch'. + + let signer = Keyring::Alice; + let signer_account: AccountId = signer.public().into(); + let authorities = [AuthorityId::::from_slice(signer_account.as_ref()).unwrap()]; + + let parentchain_header_1 = ParentchainHeaderBuilder::default().with_number(1).build(); + let parentchain_header_2 = ParentchainHeaderBuilder::default().with_number(2).build(); + + let block_data = SidechainBlockDataBuilder::default() + .with_layer_one_head(parentchain_header_1.hash()) + .with_signer(signer.pair()) + .build(); + let last_block = SidechainBlockBuilder::default() + .with_block_data(block_data) + .with_signer(signer.pair()) + .build(); + + let block_data_for_signed_block = SidechainBlockDataBuilder::default() + .with_layer_one_head(parentchain_header_1.hash()) + .with_signer(signer.pair()) + .build(); + let signed_block_to_verify = SidechainBlockBuilder::default() + .with_block_data(block_data_for_signed_block) + .with_signer(signer.pair()) + .build_signed(); + + assert_matches!( + verify_sidechain_block::( + signed_block_to_verify, + SLOT_DURATION, + &Some(last_block), + &parentchain_header_2, + &authorities, + ) + .unwrap_err(), + ConsensusError::BlockAlreadyImported(1, 1) + ); + } +} diff --git a/tee-worker/sidechain/block-verification/src/slot.rs b/tee-worker/sidechain/block-verification/src/slot.rs new file mode 100644 index 0000000000..5eb2ede417 --- /dev/null +++ b/tee-worker/sidechain/block-verification/src/slot.rs @@ -0,0 +1,45 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +use crate::AuthorityId; +pub use sp_consensus_slots::Slot; +use sp_runtime::app_crypto::Pair; +use std::time::Duration; + +/// Get slot author for given block along with authorities. +pub fn slot_author(slot: Slot, authorities: &[AuthorityId

]) -> Option<&AuthorityId

> { + if authorities.is_empty() { + log::warn!("Authorities list is empty, cannot determine slot author"); + return None + } + + let idx = *slot % (authorities.len() as u64); + assert!( + idx <= usize::MAX as u64, + "It is impossible to have a vector with length beyond the address space; qed", + ); + + let current_author = authorities.get(idx as usize).expect( + "authorities not empty; index constrained to list length;this is a valid index; qed", + ); + + Some(current_author) +} + +pub fn slot_from_timestamp_and_duration(timestamp: Duration, duration: Duration) -> Slot { + ((timestamp.as_millis() / duration.as_millis()) as u64).into() +} diff --git a/tee-worker/sidechain/consensus/aura/Cargo.toml b/tee-worker/sidechain/consensus/aura/Cargo.toml new file mode 100644 index 0000000000..6309e0d3e7 --- /dev/null +++ b/tee-worker/sidechain/consensus/aura/Cargo.toml @@ -0,0 +1,99 @@ +[package] +authors = ["Integritee AG "] +edition = "2021" +name = "its-consensus-aura" +version = "0.9.0" + +[dependencies] +codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive", "chain-error"] } +finality-grandpa = { version = "0.16.0", default-features = false, features = ["derive-codec"] } +log = { version = "0.4", default-features = false } + +# sgx deps +sgx_tstd = { branch = "master", git = "https://github.com/apache/teaclave-sgx-sdk.git", optional = true } + +# substrate deps +frame-support = { default-features = false, git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.29" } +sp-core = { default-features = false, features = ["full_crypto"], git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.29" } +sp-runtime = { default-features = false, git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.29" } + +# local deps +ita-stf = { path = "../../../app-libs/stf", default-features = false } +itc-parentchain-block-import-dispatcher = { path = "../../../core/parentchain/block-import-dispatcher", default-features = false } +itp-enclave-metrics = { path = "../../../core-primitives/enclave-metrics", default-features = false } +itp-ocall-api = { path = "../../../core-primitives/ocall-api", default-features = false } +itp-settings = { path = "../../../core-primitives/settings" } +itp-sgx-crypto = { path = "../../../core-primitives/sgx/crypto", default-features = false } +itp-sgx-externalities = { path = "../../../core-primitives/substrate-sgx/externalities", default-features = false } +itp-stf-executor = { path = "../../../core-primitives/stf-executor", default-features = false } +itp-stf-state-handler = { path = "../../../core-primitives/stf-state-handler", default-features = false } +itp-time-utils = { path = "../../../core-primitives/time-utils", default-features = false } +itp-top-pool-author = { path = "../../../core-primitives/top-pool-author", default-features = false } +itp-types = { path = "../../../core-primitives/types", default-features = false } +itp-utils = { path = "../../../core-primitives/utils", default-features = false } +its-block-composer = { path = "../../block-composer", default-features = false } +its-block-verification = { path = "../../block-verification", optional = true, default-features = false } +its-consensus-common = { path = "../common", default-features = false } +its-consensus-slots = { path = "../slots", default-features = false } +its-primitives = { path = "../../primitives", default-features = false } +its-state = { path = "../../state", default-features = false } +its-validateer-fetch = { path = "../../validateer-fetch", default-features = false } + +[dev-dependencies] +codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false } +env_logger = "0.9.0" +itc-parentchain-block-import-dispatcher = { path = "../../../core/parentchain/block-import-dispatcher", features = ["mocks"] } +itc-parentchain-test = { path = "../../../core/parentchain/test" } +itp-storage = { path = "../../../core-primitives/storage" } +itp-test = { path = "../../../core-primitives/test" } +its-test = { path = "../../../sidechain/test" } +sp-keyring = { git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.29" } + +[features] +default = ["std"] +sgx = [ + "sgx_tstd", + "ita-stf/sgx", + "itc-parentchain-block-import-dispatcher/sgx", + "itp-enclave-metrics/sgx", + "itp-sgx-crypto/sgx", + "itp-sgx-externalities/sgx", + "itp-stf-executor/sgx", + "itp-stf-state-handler/sgx", + "itp-time-utils/sgx", + "itp-utils/sgx", + "its-block-composer/sgx", + "its-consensus-common/sgx", + "its-consensus-slots/sgx", + "its-state/sgx", + "its-block-verification/sgx", +] +std = [ + #crates.io + "codec/std", + "finality-grandpa/std", + "log/std", + #substrate + "frame-support/std", + "sp-core/std", + "sp-runtime/std", + #local + "ita-stf/std", + "itc-parentchain-block-import-dispatcher/std", + "itp-enclave-metrics/std", + "itp-ocall-api/std", + "itp-sgx-crypto/std", + "itp-sgx-externalities/std", + "itp-stf-executor/std", + "itp-stf-state-handler/std", + "itp-time-utils/std", + "itp-types/std", + "itp-utils/std", + "its-block-composer/std", + "its-block-verification/std", + "its-consensus-common/std", + "its-consensus-slots/std", + "its-state/std", + "its-validateer-fetch/std", + "its-primitives/std", +] diff --git a/tee-worker/sidechain/consensus/aura/src/block_importer.rs b/tee-worker/sidechain/consensus/aura/src/block_importer.rs new file mode 100644 index 0000000000..cd44c23ed6 --- /dev/null +++ b/tee-worker/sidechain/consensus/aura/src/block_importer.rs @@ -0,0 +1,328 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ +//! Implementation of the sidechain block importer struct. +//! Imports sidechain blocks and applies the accompanying state diff to its state. + +// Reexport BlockImport trait which implements fn block_import() +pub use its_consensus_common::BlockImport; + +use crate::{AuraVerifier, EnclaveOnChainOCallApi, SidechainBlockTrait}; +use ita_stf::hash::TrustedOperationOrHash; +use itc_parentchain_block_import_dispatcher::triggered_dispatcher::TriggerParentchainBlockImport; +use itp_enclave_metrics::EnclaveMetric; +use itp_ocall_api::{EnclaveMetricsOCallApi, EnclaveSidechainOCallApi}; +use itp_settings::sidechain::SLOT_DURATION; +use itp_sgx_crypto::{key_repository::AccessKey, StateCrypto}; +use itp_sgx_externalities::SgxExternalities; +use itp_stf_state_handler::handle_state::HandleState; +use itp_top_pool_author::traits::{AuthorApi, OnBlockImported}; +use itp_types::H256; +use its_consensus_common::Error as ConsensusError; +use its_primitives::traits::{ + BlockData, Header as HeaderTrait, ShardIdentifierFor, SignedBlock as SignedBlockTrait, +}; +use its_state::SidechainDB; +use its_validateer_fetch::ValidateerFetch; +use log::*; +use sp_core::Pair; +use sp_runtime::{ + generic::SignedBlock as SignedParentchainBlock, + traits::{Block as ParentchainBlockTrait, Header}, +}; +use std::{marker::PhantomData, sync::Arc}; + +/// Implements `BlockImport`. +#[derive(Clone)] +pub struct BlockImporter< + Authority, + ParentchainBlock, + SignedSidechainBlock, + OCallApi, + SidechainState, + StateHandler, + StateKeyRepository, + TopPoolAuthor, + ParentchainBlockImporter, +> { + state_handler: Arc, + state_key_repository: Arc, + top_pool_author: Arc, + parentchain_block_importer: Arc, + ocall_api: Arc, + _phantom: PhantomData<(Authority, ParentchainBlock, SignedSidechainBlock, SidechainState)>, +} + +impl< + Authority, + ParentchainBlock, + SignedSidechainBlock, + OCallApi, + SidechainState, + StateHandler, + StateKeyRepository, + TopPoolAuthor, + ParentchainBlockImporter, + > + BlockImporter< + Authority, + ParentchainBlock, + SignedSidechainBlock, + OCallApi, + SidechainState, + StateHandler, + StateKeyRepository, + TopPoolAuthor, + ParentchainBlockImporter, + > where + Authority: Pair, + Authority::Public: std::fmt::Debug, + ParentchainBlock: ParentchainBlockTrait, + SignedSidechainBlock: SignedBlockTrait + 'static, + <::Block as SidechainBlockTrait>::HeaderType: + HeaderTrait, + OCallApi: EnclaveSidechainOCallApi + + ValidateerFetch + + EnclaveOnChainOCallApi + + EnclaveMetricsOCallApi + + Send + + Sync, + StateHandler: HandleState, + StateKeyRepository: AccessKey, + ::KeyType: StateCrypto, + TopPoolAuthor: AuthorApi + OnBlockImported, + ParentchainBlockImporter: TriggerParentchainBlockImport> + + Send + + Sync, +{ + pub fn new( + state_handler: Arc, + state_key_repository: Arc, + top_pool_author: Arc, + parentchain_block_importer: Arc, + ocall_api: Arc, + ) -> Self { + Self { + state_handler, + state_key_repository, + top_pool_author, + parentchain_block_importer, + ocall_api, + _phantom: Default::default(), + } + } + + fn update_top_pool(&self, sidechain_block: &SignedSidechainBlock::Block) { + // Notify pool about imported block for status updates of the calls. + self.top_pool_author.on_block_imported( + sidechain_block.block_data().signed_top_hashes(), + sidechain_block.hash(), + ); + + // Remove calls from pool. + let executed_operations = sidechain_block + .block_data() + .signed_top_hashes() + .iter() + .map(|hash| (TrustedOperationOrHash::Hash(*hash), true)) + .collect(); + + let _calls_failed_to_remove = self + .top_pool_author + .remove_calls_from_pool(sidechain_block.header().shard_id(), executed_operations); + + // In case the executed call did not originate in our own TOP pool, we will not be able to remove it from our TOP pool. + // So this error will occur frequently, without it meaning that something really went wrong. + // TODO: Once the TOP pools are synchronized, we will want this check again! + // for call_failed_to_remove in _calls_failed_to_remove { + // error!("Could not remove call {:?} from top pool", call_failed_to_remove); + // } + } +} + +impl< + Authority, + ParentchainBlock, + SignedSidechainBlock, + OCallApi, + StateHandler, + StateKeyRepository, + TopPoolAuthor, + ParentchainBlockImporter, + > BlockImport + for BlockImporter< + Authority, + ParentchainBlock, + SignedSidechainBlock, + OCallApi, + SidechainDB, + StateHandler, + StateKeyRepository, + TopPoolAuthor, + ParentchainBlockImporter, + > where + Authority: Pair, + Authority::Public: std::fmt::Debug, + ParentchainBlock: ParentchainBlockTrait, + SignedSidechainBlock: SignedBlockTrait + 'static, + <::Block as SidechainBlockTrait>::HeaderType: + HeaderTrait, + OCallApi: EnclaveSidechainOCallApi + + ValidateerFetch + + EnclaveOnChainOCallApi + + EnclaveMetricsOCallApi + + Send + + Sync, + StateHandler: HandleState, + StateKeyRepository: AccessKey, + ::KeyType: StateCrypto, + TopPoolAuthor: AuthorApi + OnBlockImported, + ParentchainBlockImporter: TriggerParentchainBlockImport> + + Send + + Sync, +{ + type Verifier = AuraVerifier< + Authority, + ParentchainBlock, + SignedSidechainBlock, + SidechainDB, + OCallApi, + >; + type SidechainState = SidechainDB; + type StateCrypto = ::KeyType; + type Context = OCallApi; + + fn verifier(&self, state: Self::SidechainState) -> Self::Verifier { + AuraVerifier::::new(SLOT_DURATION, state) + } + + fn apply_state_update( + &self, + shard: &ShardIdentifierFor, + mutating_function: F, + ) -> Result<(), ConsensusError> + where + F: FnOnce(Self::SidechainState) -> Result, + { + let (write_lock, state) = self + .state_handler + .load_for_mutation(shard) + .map_err(|e| ConsensusError::Other(format!("{:?}", e).into()))?; + + let updated_state = mutating_function(Self::SidechainState::new(state))?; + + self.state_handler + .write_after_mutation(updated_state.ext, write_lock, shard) + .map_err(|e| ConsensusError::Other(format!("{:?}", e).into()))?; + + Ok(()) + } + + fn verify_import( + &self, + shard: &ShardIdentifierFor, + verifying_function: F, + ) -> Result + where + F: FnOnce(Self::SidechainState) -> Result, + { + let state = self + .state_handler + .load(shard) + .map_err(|e| ConsensusError::Other(format!("{:?}", e).into()))?; + verifying_function(Self::SidechainState::new(state)) + } + + fn state_key(&self) -> Result { + self.state_key_repository + .retrieve_key() + .map_err(|e| ConsensusError::Other(format!("{:?}", e).into())) + } + + fn get_context(&self) -> &Self::Context { + &self.ocall_api + } + + fn import_parentchain_block( + &self, + sidechain_block: &SignedSidechainBlock::Block, + last_imported_parentchain_header: &ParentchainBlock::Header, + ) -> Result { + // We trigger the import of parentchain blocks up until the last one we've seen in the + // sidechain block that we're importing. This is done to prevent forks in the sidechain (#423) + let maybe_latest_imported_block = self + .parentchain_block_importer + .import_until(|signed_parentchain_block| { + signed_parentchain_block.block.hash() + == sidechain_block.block_data().layer_one_head() + }) + .map_err(|e| ConsensusError::Other(format!("{:?}", e).into()))?; + + Ok(maybe_latest_imported_block + .map(|b| b.block.header().clone()) + .unwrap_or_else(|| last_imported_parentchain_header.clone())) + } + + fn peek_parentchain_header( + &self, + sidechain_block: &SignedSidechainBlock::Block, + last_imported_parentchain_header: &ParentchainBlock::Header, + ) -> Result { + let parentchain_header_hash_to_peek = sidechain_block.block_data().layer_one_head(); + if parentchain_header_hash_to_peek == last_imported_parentchain_header.hash() { + debug!("No queue peek necessary, sidechain block references latest imported parentchain block"); + return Ok(last_imported_parentchain_header.clone()) + } + + let maybe_signed_parentchain_block = self + .parentchain_block_importer + .peek(|parentchain_block| { + parentchain_block.block.header().hash() == parentchain_header_hash_to_peek + }) + .map_err(|e| ConsensusError::Other(format!("{:?}", e).into()))?; + + maybe_signed_parentchain_block + .map(|signed_block| signed_block.block.header().clone()) + .ok_or_else(|| { + ConsensusError::Other( + format!( + "Failed to find parentchain header in import queue (hash: {}) that is \ + associated with the current sidechain block that is to be imported (number: {}, hash: {})", + parentchain_header_hash_to_peek, + sidechain_block.header().block_number(), + sidechain_block.hash() + ) + .into(), + ) + }) + } + + fn cleanup(&self, signed_sidechain_block: &SignedSidechainBlock) -> Result<(), ConsensusError> { + let sidechain_block = signed_sidechain_block.block(); + + // Remove all successfully applied trusted calls from the top pool. + self.update_top_pool(sidechain_block); + + // Send metric about sidechain block height (i.e. block number) + let block_height_metric = + EnclaveMetric::SetSidechainBlockHeight(sidechain_block.header().block_number()); + if let Err(e) = self.ocall_api.update_metric(block_height_metric) { + warn!("Failed to update sidechain block height metric: {:?}", e); + } + + Ok(()) + } +} diff --git a/tee-worker/sidechain/consensus/aura/src/lib.rs b/tee-worker/sidechain/consensus/aura/src/lib.rs new file mode 100644 index 0000000000..d8677e6aeb --- /dev/null +++ b/tee-worker/sidechain/consensus/aura/src/lib.rs @@ -0,0 +1,563 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +//! Aura worker for the sidechain. +//! +//! It is inspired by parity's implementation but has been greatly amended. + +#![cfg_attr(not(feature = "std"), no_std)] +#![cfg_attr(test, feature(assert_matches))] + +#[cfg(all(feature = "std", feature = "sgx"))] +compile_error!("feature \"std\" and feature \"sgx\" cannot be enabled at the same time"); + +#[cfg(all(not(feature = "std"), feature = "sgx"))] +#[macro_use] +extern crate sgx_tstd as std; + +use core::marker::PhantomData; +use itc_parentchain_block_import_dispatcher::triggered_dispatcher::TriggerParentchainBlockImport; +use itp_ocall_api::EnclaveOnChainOCallApi; +use itp_time_utils::duration_now; +use its_block_verification::slot::slot_author; +use its_consensus_common::{Environment, Error as ConsensusError, Proposer}; +use its_consensus_slots::{SimpleSlotWorker, Slot, SlotInfo}; +use its_primitives::{ + traits::{Block as SidechainBlockTrait, Header as HeaderTrait, SignedBlock}, + types::block::BlockHash, +}; +use its_validateer_fetch::ValidateerFetch; +use sp_core::ByteArray; +use sp_runtime::{ + app_crypto::{sp_core::H256, Pair}, + generic::SignedBlock as SignedParentchainBlock, + traits::{Block as ParentchainBlockTrait, Header as ParentchainHeaderTrait}, +}; +use std::{string::ToString, sync::Arc, time::Duration, vec::Vec}; + +pub mod block_importer; +pub mod proposer_factory; +pub mod slot_proposer; +mod verifier; + +pub use verifier::*; + +#[cfg(test)] +mod test; + +/// Aura consensus struct. +pub struct Aura< + AuthorityPair, + ParentchainBlock, + SidechainBlock, + Environment, + OcallApi, + ImportTrigger, +> { + authority_pair: AuthorityPair, + ocall_api: OcallApi, + parentchain_import_trigger: Arc, + environment: Environment, + claim_strategy: SlotClaimStrategy, + _phantom: PhantomData<(AuthorityPair, ParentchainBlock, SidechainBlock)>, +} + +impl + Aura +{ + pub fn new( + authority_pair: AuthorityPair, + ocall_api: OcallApi, + parentchain_import_trigger: Arc, + environment: Environment, + ) -> Self { + Self { + authority_pair, + ocall_api, + parentchain_import_trigger, + environment, + claim_strategy: SlotClaimStrategy::RoundRobin, + _phantom: Default::default(), + } + } + + pub fn with_claim_strategy(mut self, claim_strategy: SlotClaimStrategy) -> Self { + self.claim_strategy = claim_strategy; + + self + } +} + +/// The fraction of total block time we are allowed to be producing the block. So that we have +/// enough time send create and send the block to fellow validateers. +pub const BLOCK_PROPOSAL_SLOT_PORTION: f32 = 0.7; + +#[derive(PartialEq, Eq, Debug)] +pub enum SlotClaimStrategy { + /// try to produce a block always even if it's not the authors slot + /// Intended for first phase to see if aura production works + Always, + /// Proper Aura strategy: Only produce blocks, when it's the authors slot. + RoundRobin, +} + +type AuthorityId

=

::Public; +type ShardIdentifierFor = + <<::Block as SidechainBlockTrait>::HeaderType as HeaderTrait>::ShardIdentifier; + +impl + SimpleSlotWorker + for Aura +where + AuthorityPair: Pair, + // todo: Relax hash trait bound, but this needs a change to some other parts in the code. + ParentchainBlock: ParentchainBlockTrait, + E: Environment, + E::Proposer: Proposer, + SignedSidechainBlock: SignedBlock + Send + 'static, + OcallApi: ValidateerFetch + EnclaveOnChainOCallApi + Send + 'static, + ImportTrigger: + TriggerParentchainBlockImport>, +{ + type Proposer = E::Proposer; + type Claim = AuthorityPair::Public; + type EpochData = Vec>; + type Output = SignedSidechainBlock; + + fn logging_target(&self) -> &'static str { + "aura" + } + + fn epoch_data( + &self, + header: &ParentchainBlock::Header, + _slot: Slot, + ) -> Result { + authorities::<_, AuthorityPair, ParentchainBlock::Header>(&self.ocall_api, header) + } + + fn authorities_len(&self, epoch_data: &Self::EpochData) -> Option { + Some(epoch_data.len()) + } + + // While the header is not used in aura, it is used in different consensus systems, so it should be left there. + fn claim_slot( + &self, + _header: &ParentchainBlock::Header, + slot: Slot, + epoch_data: &Self::EpochData, + ) -> Option { + let expected_author = slot_author::(slot, epoch_data)?; + + if expected_author == &self.authority_pair.public() { + log::info!(target: self.logging_target(), "Claiming slot ({})", *slot); + return Some(self.authority_pair.public()) + } + + if self.claim_strategy == SlotClaimStrategy::Always { + log::debug!( + target: self.logging_target(), + "Not our slot but we still claim it." + ); + return Some(self.authority_pair.public()) + } + + None + } + + fn proposer( + &mut self, + header: ParentchainBlock::Header, + shard: ShardIdentifierFor, + ) -> Result { + self.environment.init(header, shard) + } + + fn proposing_remaining_duration(&self, slot_info: &SlotInfo) -> Duration { + proposing_remaining_duration(slot_info, duration_now()) + } + + fn import_parentchain_blocks_until( + &self, + parentchain_header_hash: &::Hash, + ) -> Result, ConsensusError> { + let maybe_parentchain_block = self + .parentchain_import_trigger + .import_until(|parentchain_block| { + parentchain_block.block.hash() == *parentchain_header_hash + }) + .map_err(|e| ConsensusError::Other(e.into()))?; + + Ok(maybe_parentchain_block.map(|b| b.block.header().clone())) + } + + fn peek_latest_parentchain_header( + &self, + ) -> Result, ConsensusError> { + let maybe_parentchain_block = self + .parentchain_import_trigger + .peek_latest() + .map_err(|e| ConsensusError::Other(format!("{:?}", e).into()))?; + + Ok(maybe_parentchain_block.map(|b| b.block.header().clone())) + } +} + +/// unit-testable remaining duration fn. +fn proposing_remaining_duration( + slot_info: &SlotInfo, + now: Duration, +) -> Duration { + // if a `now` before slot begin is passed such that `slot_remaining` would be bigger than `slot.slot_duration` + // we take the total `slot_duration` as reference value. + let proposing_duration = slot_info.duration.mul_f32(BLOCK_PROPOSAL_SLOT_PORTION); + + let slot_remaining = slot_info + .ends_at + .checked_sub(now) + .map(|remaining| remaining.mul_f32(BLOCK_PROPOSAL_SLOT_PORTION)) + .unwrap_or_default(); + + std::cmp::min(slot_remaining, proposing_duration) +} + +fn authorities( + ocall_api: &ValidateerFetcher, + header: &ParentchainHeader, +) -> Result>, ConsensusError> +where + ValidateerFetcher: ValidateerFetch + EnclaveOnChainOCallApi, + P: Pair, + ParentchainHeader: ParentchainHeaderTrait, +{ + Ok(ocall_api + .current_validateers(header) + .map_err(|e| ConsensusError::CouldNotGetAuthorities(e.to_string()))? + .into_iter() + .filter_map(|e| AuthorityId::

::from_slice(e.pubkey.as_ref()).ok()) + .collect()) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::test::{ + fixtures::{types::TestAura, validateer, SLOT_DURATION}, + mocks::environment_mock::EnvironmentMock, + }; + use itc_parentchain_block_import_dispatcher::trigger_parentchain_block_import_mock::TriggerParentchainBlockImportMock; + use itc_parentchain_test::{ + parentchain_block_builder::ParentchainBlockBuilder, + parentchain_header_builder::ParentchainHeaderBuilder, + }; + use itp_test::mock::onchain_mock::OnchainMock; + use itp_types::{ + Block as ParentchainBlock, Enclave, Header as ParentchainHeader, + SignedBlock as SignedParentchainBlock, + }; + use its_consensus_slots::PerShardSlotWorkerScheduler; + use sp_core::ed25519::Public; + use sp_keyring::ed25519::Keyring; + + fn get_aura( + onchain_mock: OnchainMock, + trigger_parentchain_import: Arc>, + ) -> TestAura { + Aura::new(Keyring::Alice.pair(), onchain_mock, trigger_parentchain_import, EnvironmentMock) + } + + fn get_default_aura() -> TestAura { + get_aura(Default::default(), Default::default()) + } + + fn now_slot(slot: Slot, header: &ParentchainHeader) -> SlotInfo { + let now = duration_now(); + SlotInfo { + slot, + timestamp: now, + duration: SLOT_DURATION, + ends_at: now + SLOT_DURATION, + last_imported_parentchain_head: header.clone(), + } + } + + fn now_slot_with_default_header(slot: Slot) -> SlotInfo { + now_slot(slot, &ParentchainHeaderBuilder::default().build()) + } + + fn default_authorities() -> Vec { + vec![Keyring::Alice.public(), Keyring::Bob.public(), Keyring::Charlie.public()] + } + + fn create_validateer_set_from_publics(authorities: Vec) -> Vec { + authorities.iter().map(|a| validateer(a.clone().into())).collect() + } + + fn onchain_mock( + parentchain_header: &ParentchainHeader, + authorities: Vec, + ) -> OnchainMock { + let validateers = create_validateer_set_from_publics(authorities); + OnchainMock::default().add_validateer_set(parentchain_header, Some(validateers)) + } + + fn onchain_mock_with_default_authorities_and_header() -> OnchainMock { + let parentchain_header = ParentchainHeaderBuilder::default().build(); + onchain_mock(&parentchain_header, default_authorities()) + } + + fn create_import_trigger_with_header( + header: ParentchainHeader, + ) -> Arc> { + let latest_parentchain_block = + ParentchainBlockBuilder::default().with_header(header).build_signed(); + Arc::new( + TriggerParentchainBlockImportMock::default() + .with_latest_imported(Some(latest_parentchain_block)), + ) + } + + #[test] + fn current_authority_should_claim_its_slot() { + let authorities = + vec![Keyring::Bob.public(), Keyring::Charlie.public(), Keyring::Alice.public()]; + let aura = get_default_aura(); + let header = ParentchainHeaderBuilder::default().build(); + + assert!(aura.claim_slot(&header, 0.into(), &authorities).is_none()); + assert!(aura.claim_slot(&header, 1.into(), &authorities).is_none()); + // this our authority + assert!(aura.claim_slot(&header, 2.into(), &authorities).is_some()); + + assert!(aura.claim_slot(&header, 3.into(), &authorities).is_none()); + assert!(aura.claim_slot(&header, 4.into(), &authorities).is_none()); + // this our authority + assert!(aura.claim_slot(&header, 5.into(), &authorities).is_some()); + } + + #[test] + fn current_authority_should_claim_all_slots() { + let header = ParentchainHeaderBuilder::default().build(); + let authorities = default_authorities(); + let aura = get_default_aura().with_claim_strategy(SlotClaimStrategy::Always); + + assert!(aura.claim_slot(&header, 0.into(), &authorities).is_some()); + assert!(aura.claim_slot(&header, 1.into(), &authorities).is_some()); + // this our authority + assert!(aura.claim_slot(&header, 2.into(), &authorities).is_some()); + assert!(aura.claim_slot(&header, 3.into(), &authorities).is_some()); + } + + #[test] + fn on_slot_returns_block() { + let _ = env_logger::builder().is_test(true).try_init(); + + let onchain_mock = onchain_mock_with_default_authorities_and_header(); + let mut aura = get_aura(onchain_mock, Default::default()); + + let slot_info = now_slot_with_default_header(0.into()); + + assert!(SimpleSlotWorker::on_slot(&mut aura, slot_info, Default::default()).is_some()); + } + + #[test] + fn on_slot_for_multiple_shards_returns_blocks() { + let _ = env_logger::builder().is_test(true).try_init(); + + let onchain_mock = onchain_mock_with_default_authorities_and_header(); + let mut aura = get_aura(onchain_mock, Default::default()); + + let slot_info = now_slot_with_default_header(0.into()); + + let result = PerShardSlotWorkerScheduler::on_slot( + &mut aura, + slot_info, + vec![Default::default(), Default::default()], + ); + + assert_eq!(result.len(), 2); + } + + #[test] + fn on_slot_with_nano_second_remaining_duration_does_not_panic() { + let _ = env_logger::builder().is_test(true).try_init(); + + let mut aura = get_default_aura(); + + let nano_dur = Duration::from_nanos(999); + let now = duration_now(); + + let slot_info = SlotInfo { + slot: 0.into(), + timestamp: now, + duration: nano_dur, + ends_at: now + nano_dur, + last_imported_parentchain_head: ParentchainHeaderBuilder::default().build(), + }; + + let result = PerShardSlotWorkerScheduler::on_slot( + &mut aura, + slot_info, + vec![Default::default(), Default::default()], + ); + + assert_eq!(result.len(), 0); + } + + #[test] + fn on_slot_triggers_parentchain_block_import_if_slot_is_claimed() { + let _ = env_logger::builder().is_test(true).try_init(); + let latest_parentchain_header = ParentchainHeaderBuilder::default().with_number(84).build(); + let parentchain_block_import_trigger = + create_import_trigger_with_header(latest_parentchain_header.clone()); + let authorities = default_authorities(); + + let mut aura = get_aura( + onchain_mock(&latest_parentchain_header, authorities), + parentchain_block_import_trigger.clone(), + ); + + let slot_info = now_slot(0.into(), &latest_parentchain_header); + + let result = SimpleSlotWorker::on_slot(&mut aura, slot_info, Default::default()).unwrap(); + + assert_eq!( + result.block.block.block_data().layer_one_head, + latest_parentchain_header.hash() + ); + assert!(parentchain_block_import_trigger.has_import_been_called()); + } + + #[test] + fn on_slot_does_not_trigger_parentchain_block_import_if_slot_is_not_claimed() { + let _ = env_logger::builder().is_test(true).try_init(); + let latest_parentchain_header = ParentchainHeaderBuilder::default().with_number(84).build(); + let parentchain_block_import_trigger = + create_import_trigger_with_header(latest_parentchain_header.clone()); + let authorities = default_authorities(); + + let mut aura = get_aura( + onchain_mock(&latest_parentchain_header, authorities), + parentchain_block_import_trigger.clone(), + ); + + let slot_info = now_slot(2.into(), &latest_parentchain_header); + + let result = SimpleSlotWorker::on_slot(&mut aura, slot_info, Default::default()); + + assert!(result.is_none()); + assert!(!parentchain_block_import_trigger.has_import_been_called()); + } + + #[test] + fn on_slot_claims_slot_if_latest_parentchain_header_in_queue_contains_correspondent_validateer_set( + ) { + let _ = env_logger::builder().is_test(true).try_init(); + let already_imported_parentchain_header = + ParentchainHeaderBuilder::default().with_number(84).build(); + let latest_parentchain_header = ParentchainHeaderBuilder::default().with_number(85).build(); + let parentchain_block_import_trigger = + create_import_trigger_with_header(latest_parentchain_header.clone()); + let validateer_set_one = create_validateer_set_from_publics(vec![ + Keyring::Alice.public(), + Keyring::Bob.public(), + ]); + let validateer_set_two = create_validateer_set_from_publics(vec![ + Keyring::Alice.public(), + Keyring::Bob.public(), + Keyring::Charlie.public(), + ]); + let onchain_mock = OnchainMock::default() + .add_validateer_set(&already_imported_parentchain_header, Some(validateer_set_one)) + .add_validateer_set(&latest_parentchain_header, Some(validateer_set_two)); + + let mut aura = get_aura(onchain_mock, parentchain_block_import_trigger.clone()); + + let slot_info = now_slot(3.into(), &already_imported_parentchain_header); + + let result = SimpleSlotWorker::on_slot(&mut aura, slot_info, Default::default()).unwrap(); + + assert_eq!( + result.block.block.block_data().layer_one_head, + latest_parentchain_header.hash() + ); + assert!(parentchain_block_import_trigger.has_import_been_called()); + } + + #[test] + fn on_slot_does_not_claim_slot_if_latest_parentchain_header_in_queue_contains_correspondent_validateer_set( + ) { + let _ = env_logger::builder().is_test(true).try_init(); + let already_imported_parentchain_header = + ParentchainHeaderBuilder::default().with_number(84).build(); + let latest_parentchain_header = ParentchainHeaderBuilder::default().with_number(85).build(); + let parentchain_block_import_trigger = + create_import_trigger_with_header(latest_parentchain_header.clone()); + let validateer_set_one = create_validateer_set_from_publics(vec![ + Keyring::Alice.public(), + Keyring::Bob.public(), + ]); + let validateer_set_two = create_validateer_set_from_publics(vec![ + Keyring::Alice.public(), + Keyring::Bob.public(), + Keyring::Charlie.public(), + ]); + let onchain_mock = OnchainMock::default() + .add_validateer_set(&already_imported_parentchain_header, Some(validateer_set_one)) + .add_validateer_set(&latest_parentchain_header, Some(validateer_set_two)); + + let mut aura = get_aura(onchain_mock, parentchain_block_import_trigger.clone()); + + // If the validateer set one (instead of the latest one) is looked up, the slot will be claimed. But it should not, as the latest one should be used. + let slot_info = now_slot(2.into(), &already_imported_parentchain_header); + let result = SimpleSlotWorker::on_slot(&mut aura, slot_info, Default::default()); + + assert!(result.is_none()); + assert!(!parentchain_block_import_trigger.has_import_been_called()); + } + + #[test] + fn proposing_remaining_duration_works() { + let slot_info = now_slot_with_default_header(0.into()); + + // hard to compare actual numbers but we can at least ensure that the general concept works + assert!( + proposing_remaining_duration(&slot_info, duration_now()) + < SLOT_DURATION.mul_f32(BLOCK_PROPOSAL_SLOT_PORTION + 0.01) + ); + } + + #[test] + fn proposing_remaining_duration_works_for_now_before_slot_timestamp() { + let slot_info = now_slot_with_default_header(0.into()); + + assert!( + proposing_remaining_duration(&slot_info, Duration::from_millis(0)) + < SLOT_DURATION.mul_f32(BLOCK_PROPOSAL_SLOT_PORTION + 0.01) + ); + } + + #[test] + fn proposing_remaining_duration_returns_default_if_now_after_slot() { + let slot_info = now_slot_with_default_header(0.into()); + + assert_eq!( + proposing_remaining_duration(&slot_info, duration_now() + SLOT_DURATION), + Default::default() + ); + } +} diff --git a/tee-worker/sidechain/consensus/aura/src/proposer_factory.rs b/tee-worker/sidechain/consensus/aura/src/proposer_factory.rs new file mode 100644 index 0000000000..a6589531dd --- /dev/null +++ b/tee-worker/sidechain/consensus/aura/src/proposer_factory.rs @@ -0,0 +1,115 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +use crate::slot_proposer::{ExternalitiesFor, SlotProposer}; +use codec::Encode; +use finality_grandpa::BlockNumberOps; +use itp_sgx_externalities::{SgxExternalitiesTrait, StateHash}; +use itp_stf_executor::traits::StateUpdateProposer; +use itp_top_pool_author::traits::AuthorApi; +use itp_types::H256; +use its_block_composer::ComposeBlock; +use its_consensus_common::{Environment, Error as ConsensusError}; +use its_primitives::traits::{ + Block as SidechainBlockTrait, Header as HeaderTrait, ShardIdentifierFor, + SignedBlock as SignedSidechainBlockTrait, +}; +use its_state::{SidechainState, SidechainSystemExt}; +use sp_runtime::{ + traits::{Block, NumberFor}, + MultiSignature, +}; +use std::{marker::PhantomData, sync::Arc}; + +///! `ProposerFactory` instance containing all the data to create the `SlotProposer` for the +/// next `Slot`. +pub struct ProposerFactory { + top_pool_author: Arc, + stf_executor: Arc, + block_composer: Arc, + _phantom: PhantomData, +} + +impl + ProposerFactory +{ + pub fn new( + top_pool_executor: Arc, + stf_executor: Arc, + block_composer: Arc, + ) -> Self { + Self { + top_pool_author: top_pool_executor, + stf_executor, + block_composer, + _phantom: Default::default(), + } + } +} + +impl< + ParentchainBlock: Block, + SignedSidechainBlock, + TopPoolAuthor, + StfExecutor, + BlockComposer, + > Environment + for ProposerFactory +where + NumberFor: BlockNumberOps, + SignedSidechainBlock: SignedSidechainBlockTrait + + 'static, + SignedSidechainBlock::Block: SidechainBlockTrait, + <::Block as SidechainBlockTrait>::HeaderType: + HeaderTrait, + TopPoolAuthor: AuthorApi + Send + Sync + 'static, + StfExecutor: StateUpdateProposer + Send + Sync + 'static, + ExternalitiesFor: + SgxExternalitiesTrait + SidechainState + SidechainSystemExt + StateHash, + as SgxExternalitiesTrait>::SgxExternalitiesType: Encode, + BlockComposer: ComposeBlock< + ExternalitiesFor, + ParentchainBlock, + SignedSidechainBlock = SignedSidechainBlock, + > + Send + + Sync + + 'static, +{ + type Proposer = SlotProposer< + ParentchainBlock, + SignedSidechainBlock, + TopPoolAuthor, + StfExecutor, + BlockComposer, + >; + type Error = ConsensusError; + + fn init( + &mut self, + parent_header: ParentchainBlock::Header, + shard: ShardIdentifierFor, + ) -> Result { + Ok(SlotProposer { + top_pool_author: self.top_pool_author.clone(), + stf_executor: self.stf_executor.clone(), + block_composer: self.block_composer.clone(), + parentchain_header: parent_header, + shard, + _phantom: PhantomData, + }) + } +} diff --git a/tee-worker/sidechain/consensus/aura/src/slot_proposer.rs b/tee-worker/sidechain/consensus/aura/src/slot_proposer.rs new file mode 100644 index 0000000000..77daf9b4e4 --- /dev/null +++ b/tee-worker/sidechain/consensus/aura/src/slot_proposer.rs @@ -0,0 +1,161 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +use codec::Encode; +use finality_grandpa::BlockNumberOps; +use itp_sgx_externalities::{SgxExternalitiesTrait, StateHash}; +use itp_stf_executor::traits::StateUpdateProposer; +use itp_time_utils::now_as_u64; +use itp_top_pool_author::traits::AuthorApi; +use itp_types::H256; +use its_block_composer::ComposeBlock; +use its_consensus_common::{Error as ConsensusError, Proposal, Proposer}; +use its_primitives::traits::{ + Block as SidechainBlockTrait, Header as HeaderTrait, ShardIdentifierFor, + SignedBlock as SignedSidechainBlockTrait, +}; +use its_state::{SidechainDB, SidechainState, SidechainSystemExt}; +use log::*; +use sp_runtime::{ + traits::{Block, NumberFor}, + MultiSignature, +}; +use std::{marker::PhantomData, string::ToString, sync::Arc, time::Duration, vec::Vec}; + +pub type ExternalitiesFor = ::Externalities; +///! `SlotProposer` instance that has access to everything needed to propose a sidechain block. +pub struct SlotProposer< + ParentchainBlock: Block, + SignedSidechainBlock: SignedSidechainBlockTrait, + TopPoolAuthor, + StfExecutor, + BlockComposer, +> { + pub(crate) top_pool_author: Arc, + pub(crate) stf_executor: Arc, + pub(crate) block_composer: Arc, + pub(crate) parentchain_header: ParentchainBlock::Header, + pub(crate) shard: ShardIdentifierFor, + pub(crate) _phantom: PhantomData, +} + +impl + Proposer + for SlotProposer +where + ParentchainBlock: Block, + NumberFor: BlockNumberOps, + SignedSidechainBlock: SignedSidechainBlockTrait + + 'static, + SignedSidechainBlock::Block: SidechainBlockTrait, + <::Block as SidechainBlockTrait>::HeaderType: + HeaderTrait, + StfExecutor: StateUpdateProposer, + ExternalitiesFor: + SgxExternalitiesTrait + SidechainState + SidechainSystemExt + StateHash, + as SgxExternalitiesTrait>::SgxExternalitiesType: Encode, + TopPoolAuthor: AuthorApi + Send + Sync + 'static, + BlockComposer: ComposeBlock< + ExternalitiesFor, + ParentchainBlock, + SignedSidechainBlock = SignedSidechainBlock, + > + Send + + Sync + + 'static, +{ + /// Proposes a new sidechain block. + /// + /// This includes the following steps: + /// 1) Retrieve all trusted calls from the top pool. + /// 2) Calculate a new state that will be proposed in the sidechain block. + /// 3) Compose the sidechain block and the parentchain confirmation. + fn propose( + &self, + max_duration: Duration, + ) -> Result, ConsensusError> { + let latest_parentchain_header = &self.parentchain_header; + + // 1) Retrieve trusted calls from top pool. + let trusted_calls = self.top_pool_author.get_pending_trusted_calls(self.shard); + + if !trusted_calls.is_empty() { + debug!("Got following trusted calls from pool: {:?}", trusted_calls); + } + + // 2) Execute trusted calls. + let batch_execution_result = self + .stf_executor + .propose_state_update( + &trusted_calls, + latest_parentchain_header, + &self.shard, + max_duration, + |s| { + let mut sidechain_db = SidechainDB::< + SignedSidechainBlock::Block, + ExternalitiesFor, + >::new(s); + sidechain_db.reset_events(); + sidechain_db + .set_block_number(&sidechain_db.get_block_number().map_or(1, |n| n + 1)); + sidechain_db.set_timestamp(&now_as_u64()); + sidechain_db.ext + }, + ) + .map_err(|e| ConsensusError::Other(e.to_string().into()))?; + + let parentchain_extrinsics = batch_execution_result.get_extrinsic_callbacks(); + + let executed_operation_hashes: Vec<_> = + batch_execution_result.get_executed_operation_hashes().to_vec(); + let number_executed_transactions = executed_operation_hashes.len(); + + // Remove all not successfully executed operations from the top pool. + let failed_operations = batch_execution_result.get_failed_operations(); + self.top_pool_author.remove_calls_from_pool( + self.shard, + failed_operations + .into_iter() + .map(|e| { + let is_success = e.is_success(); + (e.trusted_operation_or_hash, is_success) + }) + .collect(), + ); + + // 3) Compose sidechain block. + let sidechain_block = self + .block_composer + .compose_block( + latest_parentchain_header, + executed_operation_hashes, + self.shard, + batch_execution_result.state_hash_before_execution, + batch_execution_result.state_after_execution, + ) + .map_err(|e| ConsensusError::Other(e.to_string().into()))?; + + info!( + "Queue/Timeslot/Transactions: {:?};{};{}", + trusted_calls.len(), + max_duration.as_millis(), + number_executed_transactions + ); + + Ok(Proposal { block: sidechain_block, parentchain_effects: parentchain_extrinsics }) + } +} diff --git a/tee-worker/sidechain/consensus/aura/src/test/block_importer_tests.rs b/tee-worker/sidechain/consensus/aura/src/test/block_importer_tests.rs new file mode 100644 index 0000000000..bc8c185d3d --- /dev/null +++ b/tee-worker/sidechain/consensus/aura/src/test/block_importer_tests.rs @@ -0,0 +1,310 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +use crate::{block_importer::BlockImporter, test::fixtures::validateer, ShardIdentifierFor}; +use codec::Encode; +use core::assert_matches::assert_matches; +use itc_parentchain_block_import_dispatcher::trigger_parentchain_block_import_mock::TriggerParentchainBlockImportMock; +use itc_parentchain_test::{ + parentchain_block_builder::ParentchainBlockBuilder, + parentchain_header_builder::ParentchainHeaderBuilder, +}; +use itp_sgx_crypto::{aes::Aes, mocks::KeyRepositoryMock, StateCrypto}; +use itp_sgx_externalities::{SgxExternalities, SgxExternalitiesDiffType}; +use itp_stf_state_handler::handle_state::HandleState; +use itp_test::mock::{handle_state_mock::HandleStateMock, onchain_mock::OnchainMock}; +use itp_time_utils::{duration_now, now_as_u64}; +use itp_top_pool_author::mocks::AuthorApiMock; +use itp_types::{Block as ParentchainBlock, Header as ParentchainHeader, H256}; +use its_consensus_common::{BlockImport, Error as ConsensusError}; +use its_primitives::{ + traits::{SignBlock, SignedBlock}, + types::{Block as SidechainBlock, SignedBlock as SignedSidechainBlock}, +}; +use its_state::{SidechainDB, SidechainState, StateUpdate}; +use its_test::{ + sidechain_block_builder::SidechainBlockBuilder, + sidechain_block_data_builder::SidechainBlockDataBuilder, + sidechain_header_builder::SidechainHeaderBuilder, +}; +use sp_core::{blake2_256, ed25519::Pair}; +use sp_keyring::ed25519::Keyring; +use sp_runtime::generic::SignedBlock as SignedParentchainBlock; +use std::sync::Arc; + +type TestSidechainState = SidechainDB; +type TestTopPoolAuthor = AuthorApiMock; +type TestParentchainBlockImportTrigger = + TriggerParentchainBlockImportMock>; +type TestStateKeyRepo = KeyRepositoryMock; +type TestBlockImporter = BlockImporter< + Pair, + ParentchainBlock, + SignedSidechainBlock, + OnchainMock, + TestSidechainState, + HandleStateMock, + TestStateKeyRepo, + TestTopPoolAuthor, + TestParentchainBlockImportTrigger, +>; + +fn state_key() -> Aes { + Aes::new([3u8; 16], [0u8; 16]) +} + +fn shard() -> ShardIdentifierFor { + blake2_256(&[1, 2, 3, 4, 5, 6]).into() +} + +fn default_authority() -> Pair { + Keyring::Alice.pair() +} + +fn test_fixtures( + parentchain_header: &ParentchainHeader, + parentchain_block_import_trigger: Arc, +) -> (TestBlockImporter, Arc, Arc) { + let state_handler = Arc::new(HandleStateMock::from_shard(shard()).unwrap()); + let top_pool_author = Arc::new(TestTopPoolAuthor::default()); + let ocall_api = Arc::new(OnchainMock::default().add_validateer_set( + parentchain_header, + Some(vec![validateer(Keyring::Alice.public().into())]), + )); + let state_key_repository = Arc::new(TestStateKeyRepo::new(state_key())); + + let block_importer = TestBlockImporter::new( + state_handler.clone(), + state_key_repository, + top_pool_author.clone(), + parentchain_block_import_trigger, + ocall_api, + ); + + (block_importer, state_handler, top_pool_author) +} + +fn test_fixtures_with_default_import_trigger( + parentchain_header: &ParentchainHeader, +) -> (TestBlockImporter, Arc, Arc) { + test_fixtures(parentchain_header, Arc::new(TestParentchainBlockImportTrigger::default())) +} + +fn empty_encrypted_state_update(state_handler: &HandleStateMock) -> Vec { + let apriori_state_hash = + TestSidechainState::new(state_handler.load(&shard()).unwrap()).state_hash(); + let empty_state_diff = SgxExternalitiesDiffType::default(); + let mut state_update = + StateUpdate::new(apriori_state_hash, apriori_state_hash, empty_state_diff).encode(); + state_key().encrypt(&mut state_update).unwrap(); + state_update +} + +fn signed_block( + parentchain_header: &ParentchainHeader, + state_handler: &HandleStateMock, + signer: Pair, +) -> SignedSidechainBlock { + let state_update = empty_encrypted_state_update(state_handler); + + let header = SidechainHeaderBuilder::default() + .with_parent_hash(H256::default()) + .with_shard(shard()) + .build(); + + let block_data = SidechainBlockDataBuilder::default() + .with_timestamp(now_as_u64()) + .with_layer_one_head(parentchain_header.hash()) + .with_signer(signer.clone()) + .with_payload(state_update) + .build(); + + SidechainBlockBuilder::default() + .with_header(header) + .with_block_data(block_data) + .with_signer(signer) + .build_signed() +} + +fn default_authority_signed_block( + parentchain_header: &ParentchainHeader, + state_handler: &HandleStateMock, +) -> SignedSidechainBlock { + signed_block(parentchain_header, state_handler, default_authority()) +} + +#[test] +fn simple_block_import_works() { + let parentchain_header = ParentchainHeaderBuilder::default().build(); + let (block_importer, state_handler, _) = + test_fixtures_with_default_import_trigger(&parentchain_header); + let signed_sidechain_block = + default_authority_signed_block(&parentchain_header, state_handler.as_ref()); + + block_importer + .import_block(signed_sidechain_block, &parentchain_header) + .unwrap(); +} + +#[test] +fn block_import_with_invalid_signature_fails() { + let parentchain_header = ParentchainHeaderBuilder::default().build(); + let (block_importer, state_handler, _) = + test_fixtures_with_default_import_trigger(&parentchain_header); + + let state_update = empty_encrypted_state_update(state_handler.as_ref()); + + let header = SidechainHeaderBuilder::default() + .with_parent_hash(H256::default()) + .with_shard(shard()) + .build(); + + let block_data = SidechainBlockDataBuilder::default() + .with_timestamp(duration_now().as_millis() as u64) + .with_layer_one_head(parentchain_header.hash()) + .with_signer(Keyring::Charlie.pair()) + .with_payload(state_update) + .build(); + + let block = SidechainBlockBuilder::default() + .with_signer(Keyring::Charlie.pair()) + .with_header(header) + .with_block_data(block_data) + .build(); + + // Bob signs the block, but Charlie is set as the author -> invalid signature. + let invalid_signature_block: SignedSidechainBlock = block.sign_block(&Keyring::Bob.pair()); + + assert!(!invalid_signature_block.verify_signature()); + assert!(block_importer + .import_block(invalid_signature_block, &parentchain_header) + .is_err()); +} + +#[test] +fn block_import_with_invalid_parentchain_block_fails() { + let parentchain_header_invalid = ParentchainHeaderBuilder::default().with_number(2).build(); + let parentchain_header = ParentchainHeaderBuilder::default().with_number(10).build(); + let (block_importer, state_handler, _) = + test_fixtures_with_default_import_trigger(&parentchain_header); + + let signed_sidechain_block = + default_authority_signed_block(&parentchain_header_invalid, state_handler.as_ref()); + + assert!(block_importer + .import_block(signed_sidechain_block, &parentchain_header) + .is_err()); +} + +#[test] +fn cleanup_removes_tops_from_pool() { + let parentchain_header = ParentchainHeaderBuilder::default().build(); + let (block_importer, state_handler, top_pool_author) = + test_fixtures_with_default_import_trigger(&parentchain_header); + let signed_sidechain_block = + default_authority_signed_block(&parentchain_header, state_handler.as_ref()); + let bob_signed_sidechain_block = + signed_block(&parentchain_header, state_handler.as_ref(), Keyring::Bob.pair()); + + block_importer.cleanup(&signed_sidechain_block).unwrap(); + block_importer.cleanup(&bob_signed_sidechain_block).unwrap(); + + assert_eq!(2, *top_pool_author.remove_attempts.read().unwrap()); +} + +#[test] +fn sidechain_block_import_triggers_parentchain_block_import() { + let previous_parentchain_header = ParentchainHeaderBuilder::default().with_number(4).build(); + let latest_parentchain_header = ParentchainHeaderBuilder::default() + .with_number(5) + .with_parent_hash(previous_parentchain_header.hash()) + .build(); + + let latest_parentchain_block = ParentchainBlockBuilder::default() + .with_header(latest_parentchain_header.clone()) + .build_signed(); + + let parentchain_block_import_trigger = Arc::new( + TestParentchainBlockImportTrigger::default() + .with_latest_imported(Some(latest_parentchain_block)), + ); + let (block_importer, state_handler, _) = + test_fixtures(&latest_parentchain_header, parentchain_block_import_trigger.clone()); + + let signed_sidechain_block = + default_authority_signed_block(&latest_parentchain_header, state_handler.as_ref()); + + block_importer + .import_block(signed_sidechain_block, &previous_parentchain_header) + .unwrap(); + + assert!(parentchain_block_import_trigger.has_import_been_called()); +} + +#[test] +fn peek_parentchain_block_finds_block_in_queue() { + let previous_parentchain_header = ParentchainHeaderBuilder::default().with_number(4).build(); + let latest_parentchain_header = ParentchainHeaderBuilder::default() + .with_number(5) + .with_parent_hash(previous_parentchain_header.hash()) + .build(); + + let latest_parentchain_block = ParentchainBlockBuilder::default() + .with_header(latest_parentchain_header.clone()) + .build_signed(); + + let parentchain_block_import_trigger = Arc::new( + TestParentchainBlockImportTrigger::default() + .with_latest_imported(Some(latest_parentchain_block)), + ); + + let (block_importer, state_handler, _) = + test_fixtures(&latest_parentchain_header, parentchain_block_import_trigger); + + let signed_sidechain_block = + default_authority_signed_block(&latest_parentchain_header, state_handler.as_ref()); + + let peeked_header = block_importer + .peek_parentchain_header(&signed_sidechain_block.block, &previous_parentchain_header) + .unwrap(); + + assert_eq!(peeked_header, latest_parentchain_header); +} + +#[test] +fn peek_parentchain_block_returns_error_if_no_corresponding_block_can_be_found() { + let previous_parentchain_header = ParentchainHeaderBuilder::default().with_number(1).build(); + let latest_parentchain_header = ParentchainHeaderBuilder::default() + .with_number(2) + .with_parent_hash(previous_parentchain_header.hash()) + .build(); + + let parentchain_block_import_trigger = Arc::new( + TestParentchainBlockImportTrigger::default(), // Parentchain block import queue is empty, so nothing will be found when peeked. + ); + + let (block_importer, state_handler, _) = + test_fixtures(&latest_parentchain_header, parentchain_block_import_trigger); + + let signed_sidechain_block = + default_authority_signed_block(&latest_parentchain_header, state_handler.as_ref()); + + let peek_result = block_importer + .peek_parentchain_header(&signed_sidechain_block.block, &previous_parentchain_header); + + assert_matches!(peek_result, Err(ConsensusError::Other(_))); +} diff --git a/tee-worker/sidechain/consensus/aura/src/test/fixtures/mod.rs b/tee-worker/sidechain/consensus/aura/src/test/fixtures/mod.rs new file mode 100644 index 0000000000..54d47324fa --- /dev/null +++ b/tee-worker/sidechain/consensus/aura/src/test/fixtures/mod.rs @@ -0,0 +1,27 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +pub mod types; + +use itp_types::{AccountId, Enclave}; +use std::time::Duration; + +pub const SLOT_DURATION: Duration = Duration::from_millis(300); + +pub fn validateer(account: AccountId) -> Enclave { + Enclave::new(account, Default::default(), Default::default(), Default::default()) +} diff --git a/tee-worker/sidechain/consensus/aura/src/test/fixtures/types.rs b/tee-worker/sidechain/consensus/aura/src/test/fixtures/types.rs new file mode 100644 index 0000000000..39aa4ef3f4 --- /dev/null +++ b/tee-worker/sidechain/consensus/aura/src/test/fixtures/types.rs @@ -0,0 +1,43 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +use crate::{test::mocks::environment_mock::EnvironmentMock, Aura}; +use itc_parentchain_block_import_dispatcher::trigger_parentchain_block_import_mock::TriggerParentchainBlockImportMock; +use itp_test::mock::onchain_mock::OnchainMock; +use itp_types::Block as ParentchainBlock; +use its_primitives::{ + traits::{ + Block as SidechainBlockTrait, Header as SidechainHeaderTrait, + SignedBlock as SignedBlockTrait, + }, + types::block::SignedBlock as SignedSidechainBlock, +}; +use sp_runtime::{app_crypto::ed25519, generic::SignedBlock}; + +type AuthorityPair = ed25519::Pair; + +pub type ShardIdentifierFor = + <<::Block as SidechainBlockTrait>::HeaderType as SidechainHeaderTrait>::ShardIdentifier; + +pub type TestAura = Aura< + AuthorityPair, + ParentchainBlock, + SignedSidechainBlock, + EnvironmentMock, + OnchainMock, + TriggerParentchainBlockImportMock>, +>; diff --git a/tee-worker/sidechain/consensus/aura/src/test/mocks/environment_mock.rs b/tee-worker/sidechain/consensus/aura/src/test/mocks/environment_mock.rs new file mode 100644 index 0000000000..e34e4b40df --- /dev/null +++ b/tee-worker/sidechain/consensus/aura/src/test/mocks/environment_mock.rs @@ -0,0 +1,40 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +use crate::{ + test::{fixtures::types::ShardIdentifierFor, mocks::proposer_mock::ProposerMock}, + ConsensusError, +}; +use itp_types::{Block as ParentchainBlock, Header}; +use its_consensus_common::Environment; +use its_primitives::types::block::SignedBlock as SignedSidechainBlock; + +/// Mock proposer environment. +pub struct EnvironmentMock; + +impl Environment for EnvironmentMock { + type Proposer = ProposerMock; + type Error = ConsensusError; + + fn init( + &mut self, + header: Header, + _: ShardIdentifierFor, + ) -> Result { + Ok(ProposerMock { parentchain_header: header }) + } +} diff --git a/tee-worker/sidechain/consensus/aura/src/test/mocks/mod.rs b/tee-worker/sidechain/consensus/aura/src/test/mocks/mod.rs new file mode 100644 index 0000000000..5e14630020 --- /dev/null +++ b/tee-worker/sidechain/consensus/aura/src/test/mocks/mod.rs @@ -0,0 +1,19 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +pub mod environment_mock; +pub mod proposer_mock; diff --git a/tee-worker/sidechain/consensus/aura/src/test/mocks/proposer_mock.rs b/tee-worker/sidechain/consensus/aura/src/test/mocks/proposer_mock.rs new file mode 100644 index 0000000000..af0c13d2f9 --- /dev/null +++ b/tee-worker/sidechain/consensus/aura/src/test/mocks/proposer_mock.rs @@ -0,0 +1,48 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +use crate::ConsensusError; +use itp_types::{Block as ParentchainBlock, Header}; +use its_consensus_common::{Proposal, Proposer}; +use its_primitives::types::block::SignedBlock as SignedSidechainBlock; +use its_test::{ + sidechain_block_builder::SidechainBlockBuilder, + sidechain_block_data_builder::SidechainBlockDataBuilder, +}; +use std::time::Duration; + +pub struct ProposerMock { + pub(crate) parentchain_header: Header, +} + +impl Proposer for ProposerMock { + fn propose( + &self, + _max_duration: Duration, + ) -> Result, ConsensusError> { + Ok(Proposal { + block: { + let block_data = SidechainBlockDataBuilder::random() + .with_layer_one_head(self.parentchain_header.hash()) + .build(); + SidechainBlockBuilder::random().with_block_data(block_data).build_signed() + }, + + parentchain_effects: Default::default(), + }) + } +} diff --git a/tee-worker/sidechain/consensus/aura/src/test/mod.rs b/tee-worker/sidechain/consensus/aura/src/test/mod.rs new file mode 100644 index 0000000000..7c40ba019d --- /dev/null +++ b/tee-worker/sidechain/consensus/aura/src/test/mod.rs @@ -0,0 +1,20 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +mod block_importer_tests; +pub mod fixtures; +pub mod mocks; diff --git a/tee-worker/sidechain/consensus/aura/src/verifier.rs b/tee-worker/sidechain/consensus/aura/src/verifier.rs new file mode 100644 index 0000000000..a93abd39a4 --- /dev/null +++ b/tee-worker/sidechain/consensus/aura/src/verifier.rs @@ -0,0 +1,80 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +use crate::{authorities, EnclaveOnChainOCallApi}; +use core::marker::PhantomData; +use its_block_verification::verify_sidechain_block; +use its_consensus_common::{Error as ConsensusError, Verifier}; +use its_primitives::{ + traits::{Block as SidechainBlockTrait, SignedBlock as SignedSidechainBlockTrait}, + types::block::BlockHash, +}; +use its_state::LastBlockExt; +use its_validateer_fetch::ValidateerFetch; +use sp_runtime::{app_crypto::Pair, traits::Block as ParentchainBlockTrait}; +use std::{fmt::Debug, time::Duration}; + +#[derive(Default)] +pub struct AuraVerifier { + slot_duration: Duration, + sidechain_state: SidechainState, + _phantom: PhantomData<(AuthorityPair, ParentchainBlock, SidechainBlock, Context)>, +} + +impl + AuraVerifier +{ + pub fn new(slot_duration: Duration, sidechain_state: SidechainState) -> Self { + Self { slot_duration, sidechain_state, _phantom: Default::default() } + } +} + +impl + Verifier + for AuraVerifier +where + AuthorityPair: Pair, + AuthorityPair::Public: Debug, + // todo: Relax hash trait bound, but this needs a change to some other parts in the code. + ParentchainBlock: ParentchainBlockTrait, + SignedSidechainBlock: SignedSidechainBlockTrait + 'static, + SignedSidechainBlock::Block: SidechainBlockTrait, + SidechainState: LastBlockExt + Send + Sync, + Context: ValidateerFetch + EnclaveOnChainOCallApi + Send + Sync, +{ + type BlockImportParams = SignedSidechainBlock; + + type Context = Context; + + fn verify( + &self, + signed_block: SignedSidechainBlock, + parentchain_header: &ParentchainBlock::Header, + ctx: &Self::Context, + ) -> Result { + let authorities = + authorities::<_, AuthorityPair, ParentchainBlock::Header>(ctx, parentchain_header)?; + + Ok(verify_sidechain_block::( + signed_block, + self.slot_duration, + &self.sidechain_state.get_last_block(), + parentchain_header, + &authorities, + )?) + } +} diff --git a/tee-worker/sidechain/consensus/common/Cargo.toml b/tee-worker/sidechain/consensus/common/Cargo.toml new file mode 100644 index 0000000000..04ceee8237 --- /dev/null +++ b/tee-worker/sidechain/consensus/common/Cargo.toml @@ -0,0 +1,81 @@ +[package] +authors = ["Integritee AG "] +edition = "2021" +name = "its-consensus-common" +version = "0.9.0" + +[dependencies] +codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false } +log = { version = "0.4", default-features = false } +thiserror = { version = "1.0.26", optional = true } + +# local deps +itc-parentchain-light-client = { path = "../../../core/parentchain/light-client", default-features = false } +itp-block-import-queue = { path = "../../../core-primitives/block-import-queue", default-features = false } +itp-extrinsics-factory = { path = "../../../core-primitives/extrinsics-factory", default-features = false } +itp-node-api-metadata = { path = "../../../core-primitives/node-api/metadata", default-features = false } +itp-node-api-metadata-provider = { path = "../../../core-primitives/node-api/metadata-provider", default-features = false } +itp-ocall-api = { path = "../../../core-primitives/ocall-api", default-features = false } +itp-settings = { path = "../../../core-primitives/settings" } +itp-sgx-crypto = { path = "../../../core-primitives/sgx/crypto", default-features = false } +itp-types = { path = "../../../core-primitives/types", default-features = false } +its-block-verification = { path = "../../block-verification", optional = true, default-features = false } +its-primitives = { path = "../../primitives", default-features = false } +its-state = { path = "../../state", default-features = false } + +# sgx deps +sgx_tstd = { optional = true, git = "https://github.com/apache/teaclave-sgx-sdk.git", branch = "master" } +sgx_types = { git = "https://github.com/apache/teaclave-sgx-sdk.git", branch = "master" } +thiserror-sgx = { package = "thiserror", optional = true, git = "https://github.com/mesalock-linux/thiserror-sgx", tag = "sgx_1.1.3" } + +# substrate deps +sp-runtime = { default-features = false, git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.29" } + +[dev-dependencies] +# local +itc-parentchain-test = { path = "../../../core/parentchain/test" } +itp-sgx-externalities = { default-features = false, path = "../../../core-primitives/substrate-sgx/externalities" } +itp-test = { path = "../../../core-primitives/test" } +its-test = { path = "../../test" } + +# substrate +sp-core = { default-features = false, features = ["full_crypto"], git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.29" } + +[features] +default = ["std"] +sgx = [ + "sgx_tstd", + "thiserror-sgx", + # local + "itc-parentchain-light-client/sgx", + "itp-block-import-queue/sgx", + "itp-extrinsics-factory/sgx", + "itp-node-api-metadata-provider/sgx", + "itp-sgx-crypto/sgx", + "itp-sgx-externalities/sgx", + "its-state/sgx", + # scs + "its-block-verification/sgx", +] +std = [ + "codec/std", + "log/std", + "thiserror", + # local + "itc-parentchain-light-client/std", + "itp-block-import-queue/std", + "itp-extrinsics-factory/std", + "itp-node-api-metadata/std", + "itp-node-api-metadata-provider/std", + "itp-ocall-api/std", + "itp-sgx-crypto/std", + "itp-sgx-externalities/std", + "itp-types/std", + "its-primitives/std", + "its-block-verification/std", + "its-state/std", + # substrate + "sp-runtime/std", + # scs + "itp-types/std", +] diff --git a/tee-worker/sidechain/consensus/common/src/block_import.rs b/tee-worker/sidechain/consensus/common/src/block_import.rs new file mode 100644 index 0000000000..fa1af3b883 --- /dev/null +++ b/tee-worker/sidechain/consensus/common/src/block_import.rs @@ -0,0 +1,189 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +//! Abstraction around block import + +use crate::{Error, Verifier}; +use codec::Decode; +use itp_ocall_api::EnclaveSidechainOCallApi; +use itp_sgx_crypto::StateCrypto; +use its_primitives::traits::{ + Block as SidechainBlockTrait, BlockData, Header as HeaderTrait, ShardIdentifierFor, + SignedBlock as SignedSidechainBlockTrait, +}; +use its_state::{LastBlockExt, SidechainState}; +use log::*; +use sp_runtime::traits::Block as ParentchainBlockTrait; +use std::{time::Instant, vec::Vec}; + +pub trait BlockImport +where + ParentchainBlock: ParentchainBlockTrait, + SignedSidechainBlock: SignedSidechainBlockTrait, +{ + /// The verifier for of the respective consensus instance. + type Verifier: Verifier< + ParentchainBlock, + SignedSidechainBlock, + BlockImportParams = SignedSidechainBlock, + Context = Self::Context, + >; + + /// Context needed to derive verifier relevant data. + type SidechainState: SidechainState + LastBlockExt; + + /// Provides the cryptographic functions for our the state encryption. + type StateCrypto: StateCrypto; + + /// Context needed to derive verifier relevant data. + type Context: EnclaveSidechainOCallApi; + + /// Get a verifier instance. + fn verifier(&self, state: Self::SidechainState) -> Self::Verifier; + + /// Apply a state update by providing a mutating function. + fn apply_state_update( + &self, + shard: &ShardIdentifierFor, + mutating_function: F, + ) -> Result<(), Error> + where + F: FnOnce(Self::SidechainState) -> Result; + + /// Verify a sidechain block that is to be imported. + fn verify_import( + &self, + shard: &ShardIdentifierFor, + verifying_function: F, + ) -> Result + where + F: FnOnce(Self::SidechainState) -> Result; + + /// Key that is used for state encryption. + fn state_key(&self) -> Result; + + /// Getter for the context. + fn get_context(&self) -> &Self::Context; + + /// Import parentchain blocks up to and including the one we see in the sidechain block that + /// is scheduled for import. + /// + /// Returns the latest header. If no block was imported with the trigger, + /// we return `last_imported_parentchain_header`. + fn import_parentchain_block( + &self, + sidechain_block: &SignedSidechainBlock::Block, + last_imported_parentchain_header: &ParentchainBlock::Header, + ) -> Result; + + /// Peek the parentchain import queue for the block that is associated with a given sidechain. + /// Does not perform the import or mutate the queue. + /// + /// Warning: Be aware that peeking the parentchain block means that it is not verified (that happens upon import). + fn peek_parentchain_header( + &self, + sidechain_block: &SignedSidechainBlock::Block, + last_imported_parentchain_header: &ParentchainBlock::Header, + ) -> Result; + /// Cleanup task after import is done. + fn cleanup(&self, signed_sidechain_block: &SignedSidechainBlock) -> Result<(), Error>; + + /// Import a sidechain block and mutate state by `apply_state_update`. + fn import_block( + &self, + signed_sidechain_block: SignedSidechainBlock, + parentchain_header: &ParentchainBlock::Header, + ) -> Result { + let start_time = Instant::now(); + + let sidechain_block = signed_sidechain_block.block().clone(); + let shard = sidechain_block.header().shard_id(); + let block_number = signed_sidechain_block.block().header().block_number(); + + debug!( + "Attempting to import sidechain block (number: {}, hash: {:?}, parentchain hash: {:?})", + block_number, + signed_sidechain_block.block().hash(), + signed_sidechain_block.block().block_data().layer_one_head() + ); + + let peeked_parentchain_header = + self.peek_parentchain_header(&sidechain_block, parentchain_header) + .unwrap_or_else(|e| { + warn!("Could not peek parentchain block, returning latest parentchain block ({:?})", e); + parentchain_header.clone() + }); + + let block_import_params = self.verify_import(&shard, |state| { + let verifier = self.verifier(state); + verifier.verify( + signed_sidechain_block.clone(), + &peeked_parentchain_header, + self.get_context(), + ) + })?; + + let latest_parentchain_header = + self.import_parentchain_block(&sidechain_block, parentchain_header)?; + + let state_key = self.state_key()?; + + let state_update_start_time = Instant::now(); + self.apply_state_update(&shard, |mut state| { + let encrypted_state_diff = + block_import_params.block().block_data().encrypted_state_diff(); + + info!( + "Applying state diff for block {} of size {} bytes", + block_number, + encrypted_state_diff.len() + ); + + let update = state_update_from_encrypted(encrypted_state_diff, state_key)?; + + state.apply_state_update(&update).map_err(|e| Error::Other(e.into()))?; + + state.set_last_block(block_import_params.block()); + + Ok(state) + })?; + info!( + "Applying state update from block {} took {} ms", + block_number, + state_update_start_time.elapsed().as_millis() + ); + + self.cleanup(&signed_sidechain_block)?; + + // Store block in storage. + self.get_context().store_sidechain_blocks(vec![signed_sidechain_block])?; + + info!("Importing block {} took {} ms", block_number, start_time.elapsed().as_millis()); + + Ok(latest_parentchain_header) + } +} + +fn state_update_from_encrypted( + encrypted: &[u8], + key: Key, +) -> Result { + let mut payload: Vec = encrypted.to_vec(); + key.decrypt(&mut payload).map_err(|e| Error::Other(format!("{:?}", e).into()))?; + + Ok(Decode::decode(&mut payload.as_slice())?) +} diff --git a/tee-worker/sidechain/consensus/common/src/block_import_confirmation_handler.rs b/tee-worker/sidechain/consensus/common/src/block_import_confirmation_handler.rs new file mode 100644 index 0000000000..4fb7c72f6b --- /dev/null +++ b/tee-worker/sidechain/consensus/common/src/block_import_confirmation_handler.rs @@ -0,0 +1,130 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +use crate::error::{Error, Result}; +use itc_parentchain_light_client::{ + concurrent_access::ValidatorAccess, BlockNumberOps, ExtrinsicSender, NumberFor, +}; +use itp_extrinsics_factory::CreateExtrinsics; +use itp_node_api_metadata::pallet_sidechain::SidechainCallIndexes; +use itp_node_api_metadata_provider::AccessNodeMetadata; +use itp_settings::worker::BLOCK_NUMBER_FINALIZATION_DIFF; +use itp_types::{OpaqueCall, ShardIdentifier}; +use its_primitives::traits::Header as HeaderTrait; +use log::*; +use sp_runtime::traits::Block as ParentchainBlockTrait; +use std::{marker::PhantomData, sync::Arc}; + +/// Trait to confirm a sidechain block import. +pub trait ConfirmBlockImport { + fn confirm_import(&self, header: &SidechainHeader, shard: &ShardIdentifier) -> Result<()>; +} + +/// Creates and sends a sidechain block import confirmation extrsinic to the parentchain. +pub struct BlockImportConfirmationHandler< + ParentchainBlock, + SidechainHeader, + NodeMetadataRepository, + ExtrinsicsFactory, + ValidatorAccessor, +> { + metadata_repository: Arc, + extrinsics_factory: Arc, + validator_accessor: Arc, + _phantom: PhantomData<(ParentchainBlock, SidechainHeader)>, +} + +impl< + ParentchainBlock, + SidechainHeader, + NodeMetadataRepository, + ExtrinsicsFactory, + ValidatorAccessor, + > + BlockImportConfirmationHandler< + ParentchainBlock, + SidechainHeader, + NodeMetadataRepository, + ExtrinsicsFactory, + ValidatorAccessor, + > +{ + pub fn new( + metadata_repository: Arc, + extrinsics_factory: Arc, + validator_accessor: Arc, + ) -> Self { + Self { + metadata_repository, + extrinsics_factory, + validator_accessor, + _phantom: Default::default(), + } + } +} + +impl< + ParentchainBlock, + SidechainHeader, + NodeMetadataRepository, + ExtrinsicsFactory, + ValidatorAccessor, + > ConfirmBlockImport + for BlockImportConfirmationHandler< + ParentchainBlock, + SidechainHeader, + NodeMetadataRepository, + ExtrinsicsFactory, + ValidatorAccessor, + > where + ParentchainBlock: ParentchainBlockTrait, + NumberFor: BlockNumberOps, + SidechainHeader: HeaderTrait, + NodeMetadataRepository: AccessNodeMetadata, + NodeMetadataRepository::MetadataType: SidechainCallIndexes, + ExtrinsicsFactory: CreateExtrinsics, + ValidatorAccessor: ValidatorAccess + Send + Sync + 'static, +{ + fn confirm_import(&self, header: &SidechainHeader, shard: &ShardIdentifier) -> Result<()> { + let call = self + .metadata_repository + .get_from_metadata(|m| m.confirm_imported_sidechain_block_indexes()) + .map_err(|e| Error::Other(e.into()))? + .map_err(|e| Error::Other(format!("{:?}", e).into()))?; + + if header.block_number() == header.next_finalization_block_number() { + let opaque_call = OpaqueCall::from_tuple(&( + call, + shard, + header.block_number(), + header.next_finalization_block_number() + BLOCK_NUMBER_FINALIZATION_DIFF, + header.hash(), + )); + + let xts = self + .extrinsics_factory + .create_extrinsics(&[opaque_call], None) + .map_err(|e| Error::Other(e.into()))?; + + debug!("Sending sidechain block import confirmation extrinsic.."); + self.validator_accessor + .execute_mut_on_validator(|v| v.send_extrinsics(xts)) + .map_err(|e| Error::Other(e.into()))?; + } + Ok(()) + } +} diff --git a/tee-worker/sidechain/consensus/common/src/block_import_queue_worker.rs b/tee-worker/sidechain/consensus/common/src/block_import_queue_worker.rs new file mode 100644 index 0000000000..3687c5df3d --- /dev/null +++ b/tee-worker/sidechain/consensus/common/src/block_import_queue_worker.rs @@ -0,0 +1,120 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +use crate::{Error, Result, SyncBlockFromPeer}; +use core::marker::PhantomData; +use itp_block_import_queue::PopFromBlockQueue; +use its_primitives::traits::{Block as BlockTrait, SignedBlock as SignedSidechainBlockTrait}; +use log::debug; +use sp_runtime::traits::Block as ParentchainBlockTrait; +use std::{sync::Arc, time::Instant}; + +/// Trait to trigger working the sidechain block import queue. +pub trait ProcessBlockImportQueue { + /// Pop sidechain blocks from the import queue and import them until queue is empty. + fn process_queue( + &self, + current_parentchain_header: &ParentchainBlockHeader, + ) -> Result; +} + +pub struct BlockImportQueueWorker< + ParentchainBlock, + SignedSidechainBlock, + BlockImportQueue, + PeerBlockSyncer, +> { + block_import_queue: Arc, + peer_block_syncer: Arc, + _phantom: PhantomData<(ParentchainBlock, SignedSidechainBlock)>, +} + +impl + BlockImportQueueWorker +where + ParentchainBlock: ParentchainBlockTrait, + SignedSidechainBlock: SignedSidechainBlockTrait, + SignedSidechainBlock::Block: BlockTrait, + BlockImportQueue: PopFromBlockQueue, + PeerBlockSyncer: SyncBlockFromPeer, +{ + pub fn new( + block_import_queue: Arc, + peer_block_syncer: Arc, + ) -> Self { + BlockImportQueueWorker { + block_import_queue, + peer_block_syncer, + _phantom: Default::default(), + } + } + + fn record_timings(start_time: Instant, number_of_imported_blocks: usize) { + let elapsed_time_millis = start_time.elapsed().as_millis(); + let time_millis_per_block = + (elapsed_time_millis as f64 / number_of_imported_blocks as f64).ceil(); + debug!( + "Imported {} blocks in {} ms (average of {} ms per block)", + number_of_imported_blocks, elapsed_time_millis, time_millis_per_block + ); + } +} + +impl + ProcessBlockImportQueue + for BlockImportQueueWorker< + ParentchainBlock, + SignedSidechainBlock, + BlockImportQueue, + PeerBlockSyncer, + > where + ParentchainBlock: ParentchainBlockTrait, + SignedSidechainBlock: SignedSidechainBlockTrait, + SignedSidechainBlock::Block: BlockTrait, + BlockImportQueue: PopFromBlockQueue, + PeerBlockSyncer: SyncBlockFromPeer, +{ + fn process_queue( + &self, + current_parentchain_header: &ParentchainBlock::Header, + ) -> Result { + let mut latest_imported_parentchain_header = current_parentchain_header.clone(); + let mut number_of_imported_blocks = 0usize; + let start_time = Instant::now(); + + loop { + match self.block_import_queue.pop_front() { + Ok(maybe_block) => match maybe_block { + Some(block) => { + latest_imported_parentchain_header = self + .peer_block_syncer + .sync_block(block, &latest_imported_parentchain_header)?; + number_of_imported_blocks += 1; + }, + None => { + Self::record_timings(start_time, number_of_imported_blocks); + return Ok(latest_imported_parentchain_header) + }, + }, + Err(e) => { + Self::record_timings(start_time, number_of_imported_blocks); + return Err(Error::FailedToPopBlockImportQueue(e)) + }, + } + } + } +} diff --git a/tee-worker/sidechain/consensus/common/src/block_production_suspension.rs b/tee-worker/sidechain/consensus/common/src/block_production_suspension.rs new file mode 100644 index 0000000000..ae664925da --- /dev/null +++ b/tee-worker/sidechain/consensus/common/src/block_production_suspension.rs @@ -0,0 +1,112 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +//! Mechanisms to (temporarily) suspend the production of sidechain blocks. + +#[cfg(feature = "sgx")] +use std::sync::SgxRwLock as RwLock; + +#[cfg(feature = "std")] +use std::sync::RwLock; + +use crate::error::{Error, Result}; +use log::*; + +/// Trait to suspend the production of sidechain blocks. +pub trait SuspendBlockProduction { + /// Suspend any sidechain block production. + fn suspend_for_sync(&self) -> Result<()>; + + /// Resume block sidechain block production. + fn resume(&self) -> Result<()>; +} + +/// Trait to query if sidechain block production is suspended. +pub trait IsBlockProductionSuspended { + fn is_suspended(&self) -> Result; + + fn is_sync_ongoing(&self) -> Result; +} + +/// Implementation for suspending and resuming sidechain block production. +#[derive(Default)] +pub struct BlockProductionSuspender { + is_suspended: RwLock, + sync_is_ongoing: RwLock, +} + +impl BlockProductionSuspender { + pub fn new(is_suspended: bool) -> Self { + BlockProductionSuspender { + is_suspended: RwLock::new(is_suspended), + sync_is_ongoing: RwLock::new(false), + } + } +} + +impl SuspendBlockProduction for BlockProductionSuspender { + fn suspend_for_sync(&self) -> Result<()> { + let mut suspended_lock = self.is_suspended.write().map_err(|_| Error::LockPoisoning)?; + *suspended_lock = true; + + let mut sync_is_ongoing_lock = + self.sync_is_ongoing.write().map_err(|_| Error::LockPoisoning)?; + *sync_is_ongoing_lock = true; + + info!("Suspend sidechain block production"); + Ok(()) + } + + fn resume(&self) -> Result<()> { + let mut suspended_lock = self.is_suspended.write().map_err(|_| Error::LockPoisoning)?; + *suspended_lock = false; + info!("Resume sidechain block production"); + Ok(()) + } +} + +impl IsBlockProductionSuspended for BlockProductionSuspender { + fn is_suspended(&self) -> Result { + Ok(*self.is_suspended.read().map_err(|_| Error::LockPoisoning)?) + } + + fn is_sync_ongoing(&self) -> Result { + Ok(*self.sync_is_ongoing.read().map_err(|_| Error::LockPoisoning)?) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn initial_production_is_not_suspended() { + let block_production_suspender = BlockProductionSuspender::default(); + assert!(!block_production_suspender.is_suspended().unwrap()); + } + + #[test] + fn suspending_production_works() { + let block_production_suspender = BlockProductionSuspender::default(); + + block_production_suspender.suspend_for_sync().unwrap(); + assert!(block_production_suspender.is_suspended().unwrap()); + + block_production_suspender.resume().unwrap(); + assert!(!block_production_suspender.is_suspended().unwrap()); + } +} diff --git a/tee-worker/sidechain/consensus/common/src/error.rs b/tee-worker/sidechain/consensus/common/src/error.rs new file mode 100644 index 0000000000..1f1b1f21f8 --- /dev/null +++ b/tee-worker/sidechain/consensus/common/src/error.rs @@ -0,0 +1,99 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +//! Error types in sidechain consensus + +use itp_types::BlockHash as ParentchainBlockHash; +use its_block_verification::error::Error as VerificationError; +use its_primitives::types::{block::BlockHash as SidechainBlockHash, BlockNumber}; +use sgx_types::sgx_status_t; +use std::{ + boxed::Box, + error, + string::{String, ToString}, + vec::Vec, +}; + +pub type Result = std::result::Result; + +#[cfg(all(not(feature = "std"), feature = "sgx"))] +pub use thiserror_sgx as thiserror; + +#[derive(Debug, thiserror::Error)] +#[non_exhaustive] +pub enum Error { + #[error("SGX error, status: {0}")] + Sgx(sgx_status_t), + #[error("Unable to create block proposal.")] + CannotPropose, + #[error("Encountered poisoned lock")] + LockPoisoning, + #[error("Message sender {0} is not a valid authority")] + InvalidAuthority(String), + #[error("Could not get authorities: {0:?}.")] + CouldNotGetAuthorities(String), + #[error(transparent)] + Other(#[from] Box), + #[error("Chain lookup failed: {0}")] + ChainLookup(String), + #[error("Failed to sign using key: {0:?}. Reason: {1}")] + CannotSign(Vec, String), + #[error("Bad parentchain block (Hash={0}). Reason: {1}")] + BadParentchainBlock(ParentchainBlockHash, String), + #[error("Bad sidechain block (Hash={0}). Reason: {1}")] + BadSidechainBlock(SidechainBlockHash, String), + #[error("Could not import new block due to {2}. (Last imported by number: {0:?})")] + BlockAncestryMismatch(BlockNumber, SidechainBlockHash, String), + #[error("Could not import new block. Expected first block, but found {0}. {1:?}")] + InvalidFirstBlock(BlockNumber, String), + #[error("Could not import block (number: {0}). A block with this number is already imported (current state block number: {1})")] + BlockAlreadyImported(BlockNumber, BlockNumber), + #[error("Failed to pop from block import queue: {0}")] + FailedToPopBlockImportQueue(#[from] itp_block_import_queue::error::Error), + #[error("Verification Error: {0}")] + VerificationError(its_block_verification::error::Error), +} + +impl core::convert::From for Error { + fn from(e: std::io::Error) -> Self { + Self::Other(e.into()) + } +} + +impl core::convert::From for Error { + fn from(e: codec::Error) -> Self { + Self::Other(e.to_string().into()) + } +} + +impl From for Error { + fn from(sgx_status: sgx_status_t) -> Self { + Self::Sgx(sgx_status) + } +} + +impl From for Error { + fn from(e: VerificationError) -> Self { + match e { + VerificationError::BlockAncestryMismatch(a, b, c) => + Error::BlockAncestryMismatch(a, b, c), + VerificationError::InvalidFirstBlock(a, b) => Error::InvalidFirstBlock(a, b), + VerificationError::BlockAlreadyImported(a, b) => Error::BlockAlreadyImported(a, b), + _ => Error::VerificationError(e), + } + } +} diff --git a/tee-worker/sidechain/consensus/common/src/lib.rs b/tee-worker/sidechain/consensus/common/src/lib.rs new file mode 100644 index 0000000000..60ce5d17e3 --- /dev/null +++ b/tee-worker/sidechain/consensus/common/src/lib.rs @@ -0,0 +1,108 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +//! Common stuff that could be shared across multiple consensus engines + +#![cfg_attr(not(feature = "std"), no_std)] +#![cfg_attr(test, feature(assert_matches))] + +#[cfg(all(feature = "std", feature = "sgx"))] +compile_error!("feature \"std\" and feature \"sgx\" cannot be enabled at the same time"); + +#[cfg(all(not(feature = "std"), feature = "sgx"))] +#[macro_use] +extern crate sgx_tstd as std; + +use itp_types::OpaqueCall; +use its_primitives::traits::{ShardIdentifierFor, SignedBlock as SignedSidechainBlockTrait}; +use sp_runtime::traits::Block as ParentchainBlockTrait; +use std::{time::Duration, vec::Vec}; + +mod block_import; +mod block_import_confirmation_handler; +mod block_import_queue_worker; +mod error; +mod peer_block_sync; + +#[cfg(test)] +mod test; + +pub use block_import::*; +pub use block_import_confirmation_handler::*; +pub use block_import_queue_worker::*; +pub use error::*; +pub use peer_block_sync::*; + +pub trait Verifier: Send + Sync +where + ParentchainBlock: ParentchainBlockTrait, + SignedSidechainBlock: SignedSidechainBlockTrait, +{ + /// Contains all the relevant data needed for block import + type BlockImportParams; + + /// Context used to derive slot relevant data + type Context; + + /// Verify the given data and return the `BlockImportParams` if successful + fn verify( + &self, + block: SignedSidechainBlock, + parentchain_header: &ParentchainBlock::Header, + ctx: &Self::Context, + ) -> Result; +} + +/// Environment for a Consensus instance. +/// +/// Creates proposer instance. +pub trait Environment< + ParentchainBlock: ParentchainBlockTrait, + SignedSidechainBlock: SignedSidechainBlockTrait, +> +{ + /// The proposer type this creates. + type Proposer: Proposer + Send; + /// Error which can occur upon creation. + type Error: From + std::fmt::Debug + 'static; + + /// Initialize the proposal logic on top of a specific header. + fn init( + &mut self, + parent_header: ParentchainBlock::Header, + shard: ShardIdentifierFor, + ) -> std::result::Result; +} + +pub trait Proposer< + ParentchainBlock: ParentchainBlockTrait, + SignedSidechainBlock: SignedSidechainBlockTrait, +> +{ + fn propose(&self, max_duration: Duration) -> Result>; +} + +/// A proposal that is created by a [`Proposer`]. +pub struct Proposal { + /// The sidechain block that was build. + pub block: SignedSidechainBlock, + /// Parentchain state transitions triggered by sidechain state transitions. + /// + /// Any sidechain stf that invokes a parentchain stf must not commit its state change + /// before the parentchain effect has been finalized. + pub parentchain_effects: Vec, +} diff --git a/tee-worker/sidechain/consensus/common/src/peer_block_sync.rs b/tee-worker/sidechain/consensus/common/src/peer_block_sync.rs new file mode 100644 index 0000000000..f8fe7c86f1 --- /dev/null +++ b/tee-worker/sidechain/consensus/common/src/peer_block_sync.rs @@ -0,0 +1,320 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +use crate::{BlockImport, ConfirmBlockImport, Error, Result}; +use core::marker::PhantomData; +use itp_ocall_api::EnclaveSidechainOCallApi; +use itp_types::H256; +use its_primitives::{ + traits::{ + Block as BlockTrait, Header as HeaderTrait, ShardIdentifierFor, + SignedBlock as SignedSidechainBlockTrait, + }, + types::BlockHash, +}; +use log::*; +use sp_runtime::traits::{Block as ParentchainBlockTrait, Header as ParentchainHeaderTrait}; +use std::{sync::Arc, vec::Vec}; + +/// Trait for syncing sidechain blocks from a peer validateer. +/// +/// This entails importing blocks and detecting if we're out of date with our blocks, in which +/// case we fetch the missing blocks from a peer. +pub trait SyncBlockFromPeer +where + ParentchainHeader: ParentchainHeaderTrait, + SignedSidechainBlock: SignedSidechainBlockTrait, +{ + fn sync_block( + &self, + sidechain_block: SignedSidechainBlock, + last_imported_parentchain_header: &ParentchainHeader, + ) -> Result; +} + +/// Sidechain peer block sync implementation. +pub struct PeerBlockSync< + ParentchainBlock, + SignedSidechainBlock, + BlockImporter, + SidechainOCallApi, + ImportConfirmationHandler, +> { + importer: Arc, + sidechain_ocall_api: Arc, + import_confirmation_handler: Arc, + _phantom: PhantomData<(ParentchainBlock, SignedSidechainBlock)>, +} + +impl< + ParentchainBlock, + SignedSidechainBlock, + BlockImporter, + SidechainOCallApi, + ImportConfirmationHandler, + > + PeerBlockSync< + ParentchainBlock, + SignedSidechainBlock, + BlockImporter, + SidechainOCallApi, + ImportConfirmationHandler, + > where + ParentchainBlock: ParentchainBlockTrait, + SignedSidechainBlock: SignedSidechainBlockTrait, + <::Block as BlockTrait>::HeaderType: + HeaderTrait, + BlockImporter: BlockImport, + SidechainOCallApi: EnclaveSidechainOCallApi, + ImportConfirmationHandler: ConfirmBlockImport< + <::Block as BlockTrait>::HeaderType, + >, +{ + pub fn new( + importer: Arc, + sidechain_ocall_api: Arc, + import_confirmation_handler: Arc, + ) -> Self { + PeerBlockSync { + importer, + sidechain_ocall_api, + import_confirmation_handler, + _phantom: Default::default(), + } + } + + fn fetch_and_import_blocks_from_peer( + &self, + last_imported_sidechain_block_hash: BlockHash, + import_until_block_hash: BlockHash, + current_parentchain_header: &ParentchainBlock::Header, + shard_identifier: ShardIdentifierFor, + ) -> Result { + info!( + "Initiating fetch blocks from peer, last imported block hash: {:?}, until block hash: {:?}", + last_imported_sidechain_block_hash, import_until_block_hash + ); + + let blocks_to_import: Vec = + self.sidechain_ocall_api.fetch_sidechain_blocks_from_peer( + last_imported_sidechain_block_hash, + Some(import_until_block_hash), + shard_identifier, + )?; + + info!("Fetched {} blocks from peer to import", blocks_to_import.len()); + + let mut latest_imported_parentchain_header = current_parentchain_header.clone(); + + for block_to_import in blocks_to_import { + let block_number = block_to_import.block().header().block_number(); + + latest_imported_parentchain_header = match self + .importer + .import_block(block_to_import, &latest_imported_parentchain_header) + { + Err(e) => { + error!("Failed to import sidechain block that was fetched from peer: {:?}", e); + return Err(e) + }, + Ok(h) => { + info!( + "Successfully imported peer fetched sidechain block (number: {})", + block_number + ); + h + }, + }; + } + + Ok(latest_imported_parentchain_header) + } +} + +impl + SyncBlockFromPeer + for PeerBlockSync +where + ParentchainBlock: ParentchainBlockTrait, + SignedSidechainBlock: SignedSidechainBlockTrait, + <::Block as BlockTrait>::HeaderType: + HeaderTrait, + BlockImporter: BlockImport, + SidechainOCallApi: EnclaveSidechainOCallApi, + ImportConfirmationHandler: ConfirmBlockImport<<::Block as BlockTrait>::HeaderType>, +{ + fn sync_block( + &self, + sidechain_block: SignedSidechainBlock, + current_parentchain_header: &ParentchainBlock::Header, + ) -> Result { + let shard_identifier = sidechain_block.block().header().shard_id(); + let sidechain_block_number = sidechain_block.block().header().block_number(); + let sidechain_block_hash = sidechain_block.hash(); + + // Attempt to import the block - in case we encounter an ancestry error, we go into + // peer fetching mode to fetch sidechain blocks from a peer and import those first. + match self.importer.import_block(sidechain_block.clone(), current_parentchain_header) { + Err(e) => match e { + Error::BlockAncestryMismatch(_block_number, block_hash, _) => { + warn!("Got ancestry mismatch error upon block import. Attempting to fetch missing blocks from peer"); + let updated_parentchain_header = self.fetch_and_import_blocks_from_peer( + block_hash, + sidechain_block_hash, + current_parentchain_header, + shard_identifier, + )?; + + self.importer.import_block(sidechain_block, &updated_parentchain_header) + }, + Error::InvalidFirstBlock(block_number, _) => { + warn!("Got invalid first block error upon block import (expected first block, but got block with number {}). \ + Attempting to fetch missing blocks from peer", block_number); + let updated_parentchain_header = self.fetch_and_import_blocks_from_peer( + Default::default(), // This is the parent hash of the first block. So we import everything. + sidechain_block_hash, + current_parentchain_header, + shard_identifier, + )?; + + self.importer.import_block(sidechain_block, &updated_parentchain_header) + }, + Error::BlockAlreadyImported(to_import_block_number, last_known_block_number) => { + warn!("Sidechain block from queue (number: {}) was already imported (current block number: {}). Block will be ignored.", + to_import_block_number, last_known_block_number); + Ok(current_parentchain_header.clone()) + }, + _ => Err(e), + }, + Ok(latest_parentchain_header) => { + info!("Successfully imported broadcast sidechain block (number: {}), based on parentchain block {:?}", + sidechain_block_number, latest_parentchain_header.number()); + + // We confirm the successful block import. Only in this case, not when we're in + // on-boarding and importing blocks that were fetched from a peer. + if let Err(e) = self.import_confirmation_handler.confirm_import(sidechain_block.block().header(), &shard_identifier) { + error!("Failed to confirm sidechain block import: {:?}", e); + } + + Ok(latest_parentchain_header) + }, + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::test::mocks::{ + block_importer_mock::BlockImportMock, confirm_block_import_mock::ConfirmBlockImportMock, + }; + use core::assert_matches::assert_matches; + use itc_parentchain_test::parentchain_header_builder::ParentchainHeaderBuilder; + use itp_test::mock::sidechain_ocall_api_mock::SidechainOCallApiMock; + use itp_types::Block as ParentchainBlock; + use its_primitives::types::block::SignedBlock as SignedSidechainBlock; + use its_test::sidechain_block_builder::SidechainBlockBuilder; + + type TestBlockImport = BlockImportMock; + type TestOCallApi = SidechainOCallApiMock; + type TestPeerBlockSync = PeerBlockSync< + ParentchainBlock, + SignedSidechainBlock, + TestBlockImport, + TestOCallApi, + ConfirmBlockImportMock, + >; + + #[test] + fn if_block_import_is_successful_no_peer_fetching_happens() { + let parentchain_header = ParentchainHeaderBuilder::default().build(); + let signed_sidechain_block = SidechainBlockBuilder::default().build_signed(); + + let block_importer_mock = Arc::new( + BlockImportMock::::default() + .with_import_result_once(Ok(parentchain_header.clone())), + ); + + let sidechain_ocall_api = + Arc::new(SidechainOCallApiMock::::default()); + + let peer_syncer = + create_peer_syncer(block_importer_mock.clone(), sidechain_ocall_api.clone()); + + peer_syncer.sync_block(signed_sidechain_block, &parentchain_header).unwrap(); + + assert_eq!(1, block_importer_mock.get_imported_blocks().len()); + assert_eq!(0, sidechain_ocall_api.number_of_fetch_calls()); + } + + #[test] + fn error_is_propagated_if_import_returns_error_other_than_ancestry_mismatch() { + let block_importer_mock = Arc::new( + BlockImportMock::::default() + .with_import_result_once(Err(Error::InvalidAuthority("auth".to_string()))), + ); + + let sidechain_ocall_api = + Arc::new(SidechainOCallApiMock::::default()); + + let peer_syncer = + create_peer_syncer(block_importer_mock.clone(), sidechain_ocall_api.clone()); + + let parentchain_header = ParentchainHeaderBuilder::default().build(); + let signed_sidechain_block = SidechainBlockBuilder::default().build_signed(); + + let sync_result = peer_syncer.sync_block(signed_sidechain_block, &parentchain_header); + + assert_matches!(sync_result, Err(Error::InvalidAuthority(_))); + assert_eq!(1, block_importer_mock.get_imported_blocks().len()); + assert_eq!(0, sidechain_ocall_api.number_of_fetch_calls()); + } + + #[test] + fn blocks_are_fetched_from_peer_if_initial_import_yields_ancestry_mismatch() { + let block_importer_mock = + Arc::new(BlockImportMock::::default().with_import_result_once( + Err(Error::BlockAncestryMismatch(1, H256::random(), "".to_string())), + )); + + let sidechain_ocall_api = Arc::new( + SidechainOCallApiMock::::default().with_peer_fetch_blocks(vec![ + SidechainBlockBuilder::random().build_signed(), + SidechainBlockBuilder::random().build_signed(), + ]), + ); + + let peer_syncer = + create_peer_syncer(block_importer_mock.clone(), sidechain_ocall_api.clone()); + + let parentchain_header = ParentchainHeaderBuilder::default().build(); + let signed_sidechain_block = SidechainBlockBuilder::default().build_signed(); + + peer_syncer.sync_block(signed_sidechain_block, &parentchain_header).unwrap(); + + assert_eq!(4, block_importer_mock.get_imported_blocks().len()); + assert_eq!(1, sidechain_ocall_api.number_of_fetch_calls()); + } + + fn create_peer_syncer( + block_importer: Arc, + ocall_api: Arc, + ) -> TestPeerBlockSync { + let import_confirmation_handler = Arc::new(ConfirmBlockImportMock {}); + TestPeerBlockSync::new(block_importer, ocall_api, import_confirmation_handler) + } +} diff --git a/tee-worker/sidechain/consensus/common/src/test/mocks/block_importer_mock.rs b/tee-worker/sidechain/consensus/common/src/test/mocks/block_importer_mock.rs new file mode 100644 index 0000000000..3dab353bb8 --- /dev/null +++ b/tee-worker/sidechain/consensus/common/src/test/mocks/block_importer_mock.rs @@ -0,0 +1,165 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +use crate::{test::mocks::verifier_mock::VerifierMock, BlockImport, Error, Result}; +use core::marker::PhantomData; +use itp_sgx_crypto::aes::Aes; +use itp_sgx_externalities::SgxExternalities; +use itp_test::mock::onchain_mock::OnchainMock; +use itp_types::H256; +use its_primitives::traits::{ShardIdentifierFor, SignedBlock as SignedSidechainBlockTrait}; +use its_state::SidechainDB; +use sp_core::Pair; +use sp_runtime::traits::Block as ParentchainBlockTrait; +use std::{collections::VecDeque, sync::RwLock}; + +/// Block importer mock. +pub struct BlockImportMock +where + ParentchainBlock: ParentchainBlockTrait, + SignedSidechainBlock: + SignedSidechainBlockTrait::Public> + 'static, +{ + import_result: RwLock>>, + imported_blocks: RwLock>, + _phantom: PhantomData<(ParentchainBlock, SignedSidechainBlock)>, +} + +impl BlockImportMock +where + ParentchainBlock: ParentchainBlockTrait, + SignedSidechainBlock: + SignedSidechainBlockTrait::Public> + 'static, +{ + pub fn with_import_result_once(self, result: Result) -> Self { + let mut imported_results_lock = self.import_result.write().unwrap(); + imported_results_lock.push_back(result); + std::mem::drop(imported_results_lock); + self + } + + #[allow(unused)] + pub fn with_import_result_sequence( + self, + mut results: VecDeque>, + ) -> Self { + let mut imported_results_lock = self.import_result.write().unwrap(); + imported_results_lock.append(&mut results); + std::mem::drop(imported_results_lock); + self + } + + pub fn get_imported_blocks(&self) -> Vec { + (*self.imported_blocks.read().unwrap()).clone() + } +} + +impl Default + for BlockImportMock +where + ParentchainBlock: ParentchainBlockTrait, + SignedSidechainBlock: + SignedSidechainBlockTrait::Public> + 'static, +{ + fn default() -> Self { + BlockImportMock { + import_result: RwLock::default(), + imported_blocks: RwLock::default(), + _phantom: Default::default(), + } + } +} + +impl BlockImport + for BlockImportMock +where + ParentchainBlock: ParentchainBlockTrait, + SignedSidechainBlock: + SignedSidechainBlockTrait::Public> + 'static, +{ + type Verifier = + VerifierMock; + type SidechainState = SidechainDB; + type StateCrypto = Aes; + type Context = OnchainMock; + + fn verifier(&self, _state: Self::SidechainState) -> Self::Verifier { + todo!() + } + + fn apply_state_update( + &self, + _shard: &ShardIdentifierFor, + _mutating_function: F, + ) -> Result<()> + where + F: FnOnce(Self::SidechainState) -> Result, + { + todo!() + } + + fn verify_import( + &self, + _shard: &ShardIdentifierFor, + _verifying_function: F, + ) -> core::result::Result + where + F: FnOnce(Self::SidechainState) -> core::result::Result, + { + todo!() + } + + fn state_key(&self) -> Result { + todo!() + } + + fn get_context(&self) -> &Self::Context { + todo!() + } + + fn import_parentchain_block( + &self, + _sidechain_block: &SignedSidechainBlock::Block, + _last_imported_parentchain_header: &ParentchainBlock::Header, + ) -> Result { + todo!() + } + + fn peek_parentchain_header( + &self, + _sidechain_block: &SignedSidechainBlock::Block, + _last_imported_parentchain_header: &ParentchainBlock::Header, + ) -> core::result::Result { + todo!() + } + + fn cleanup(&self, _signed_sidechain_block: &SignedSidechainBlock) -> Result<()> { + todo!() + } + + fn import_block( + &self, + signed_sidechain_block: SignedSidechainBlock, + parentchain_header: &ParentchainBlock::Header, + ) -> Result { + let mut imported_blocks_lock = self.imported_blocks.write().unwrap(); + imported_blocks_lock.push(signed_sidechain_block); + + let mut imported_results_lock = self.import_result.write().unwrap(); + imported_results_lock.pop_front().unwrap_or(Ok(parentchain_header.clone())) + } +} diff --git a/tee-worker/sidechain/consensus/common/src/test/mocks/confirm_block_import_mock.rs b/tee-worker/sidechain/consensus/common/src/test/mocks/confirm_block_import_mock.rs new file mode 100644 index 0000000000..a810da2f3b --- /dev/null +++ b/tee-worker/sidechain/consensus/common/src/test/mocks/confirm_block_import_mock.rs @@ -0,0 +1,29 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +use crate::{error::Result, ConfirmBlockImport}; +use itp_types::ShardIdentifier; +use its_primitives::types::header::SidechainHeader; + +/// Mock implementation of the `ConfirmBlockImport` trait. +pub struct ConfirmBlockImportMock; + +impl ConfirmBlockImport for ConfirmBlockImportMock { + fn confirm_import(&self, _header: &SidechainHeader, _shard: &ShardIdentifier) -> Result<()> { + Ok(()) + } +} diff --git a/tee-worker/sidechain/consensus/common/src/test/mocks/mod.rs b/tee-worker/sidechain/consensus/common/src/test/mocks/mod.rs new file mode 100644 index 0000000000..39137ee822 --- /dev/null +++ b/tee-worker/sidechain/consensus/common/src/test/mocks/mod.rs @@ -0,0 +1,20 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +pub mod block_importer_mock; +pub mod confirm_block_import_mock; +pub mod verifier_mock; diff --git a/tee-worker/sidechain/consensus/common/src/test/mocks/verifier_mock.rs b/tee-worker/sidechain/consensus/common/src/test/mocks/verifier_mock.rs new file mode 100644 index 0000000000..6e104574ea --- /dev/null +++ b/tee-worker/sidechain/consensus/common/src/test/mocks/verifier_mock.rs @@ -0,0 +1,61 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +use crate::{Result, Verifier}; +use itp_types::H256; +use its_primitives::traits::SignedBlock as SignedSidechainBlockTrait; +use sp_core::Pair; +use sp_runtime::traits::Block as ParentchainBlockTrait; +use std::marker::PhantomData; + +/// Verifier mock implementation. +pub struct VerifierMock< + ParentchainBlock, + SignedSidechainBlock, + BlockImportParameters, + VerifierContext, +> { + _phantom: PhantomData<( + ParentchainBlock, + SignedSidechainBlock, + BlockImportParameters, + VerifierContext, + )>, +} + +impl + Verifier + for VerifierMock +where + ParentchainBlock: ParentchainBlockTrait, + SignedSidechainBlock: + SignedSidechainBlockTrait::Public> + 'static, + BlockImportParameters: Send + Sync, + VerifierContext: Send + Sync, +{ + type BlockImportParams = BlockImportParameters; + type Context = VerifierContext; + + fn verify( + &self, + _block: SignedSidechainBlock, + _parentchain_header: &ParentchainBlock::Header, + _ctx: &Self::Context, + ) -> Result { + todo!() + } +} diff --git a/tee-worker/sidechain/consensus/common/src/test/mod.rs b/tee-worker/sidechain/consensus/common/src/test/mod.rs new file mode 100644 index 0000000000..43e6cb274d --- /dev/null +++ b/tee-worker/sidechain/consensus/common/src/test/mod.rs @@ -0,0 +1,18 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +pub mod mocks; diff --git a/tee-worker/sidechain/consensus/slots/Cargo.toml b/tee-worker/sidechain/consensus/slots/Cargo.toml new file mode 100644 index 0000000000..44ed40304f --- /dev/null +++ b/tee-worker/sidechain/consensus/slots/Cargo.toml @@ -0,0 +1,67 @@ +[package] +authors = ["Integritee AG "] +edition = "2021" +name = "its-consensus-slots" +version = "0.9.0" + + +[dependencies] +codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } +derive_more = "0.99.16" +lazy_static = { version = "1.1.0", features = ["spin_no_std"] } +log = { version = "0.4", default-features = false } + +# local deps +itp-types = { path = "../../../core-primitives/types", default-features = false } +its-block-verification = { path = "../../block-verification", default-features = false } +its-primitives = { path = "../../primitives", default-features = false } + +# only for slot-stream +futures = { version = "0.3", optional = true } +futures-timer = { version = "3.0", optional = true } + +# sgx deps +sgx_tstd = { branch = "master", git = "https://github.com/apache/teaclave-sgx-sdk.git", optional = true, features = ["untrusted_time"] } + +# substrate deps +sp-consensus-slots = { default-features = false, git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.29" } +sp-runtime = { default-features = false, git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.29" } + +# local deps +itp-settings = { path = "../../../core-primitives/settings" } +itp-sgx-io = { path = "../../../core-primitives/sgx/io", default-features = false } +itp-time-utils = { path = "../../../core-primitives/time-utils", default-features = false } +its-consensus-common = { path = "../common", default-features = false } + +[dev-dependencies] +itc-parentchain-test = { path = "../../../core/parentchain/test" } +its-test = { path = "../../test" } +sp-keyring = { git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.29" } +tokio = { version = "1.6.1", features = ["full"] } + + +[features] +default = ["std"] +sgx = [ + "itp-sgx-io/sgx", + "itp-time-utils/sgx", + "its-consensus-common/sgx", + "sgx_tstd", +] +std = [ + "codec/std", + "log/std", + # only for slot-stream + "futures", + "futures-timer", + # substrate + "sp-consensus-slots/std", + "sp-runtime/std", + # local + "itp-sgx-io/std", + "itp-time-utils/std", + "itp-types/std", + "its-primitives/std", + "its-block-verification/std", + "its-consensus-common/std", +] diff --git a/tee-worker/sidechain/consensus/slots/src/lib.rs b/tee-worker/sidechain/consensus/slots/src/lib.rs new file mode 100644 index 0000000000..1f1b000ebe --- /dev/null +++ b/tee-worker/sidechain/consensus/slots/src/lib.rs @@ -0,0 +1,371 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +//! Slots functionality for the integritee-sidechain. +//! +//! Some consensus algorithms have a concept of *slots*, which are intervals in +//! time during which certain events can and/or must occur. This crate +//! provides generic functionality for slots. + +#![cfg_attr(not(feature = "std"), no_std)] +#![cfg_attr(test, feature(assert_matches))] + +#[cfg(all(feature = "std", feature = "sgx"))] +compile_error!("feature \"std\" and feature \"sgx\" cannot be enabled at the same time"); + +#[cfg(all(not(feature = "std"), feature = "sgx"))] +#[macro_use] +extern crate sgx_tstd as std; + +use codec::Encode; +use derive_more::From; +use itp_time_utils::{duration_difference, duration_now}; +use itp_types::OpaqueCall; +use its_consensus_common::{Error as ConsensusError, Proposer}; +use its_primitives::traits::{ + Block as SidechainBlockTrait, Header as HeaderTrait, ShardIdentifierFor, + SignedBlock as SignedSidechainBlockTrait, +}; +use log::*; +pub use slots::*; +use sp_runtime::traits::{Block as ParentchainBlockTrait, Header as ParentchainHeaderTrait}; +use std::{fmt::Debug, time::Duration, vec::Vec}; + +#[cfg(feature = "std")] +mod slot_stream; +mod slots; + +#[cfg(test)] +mod mocks; + +#[cfg(test)] +mod per_shard_slot_worker_tests; + +#[cfg(feature = "std")] +pub use slot_stream::*; +pub use slots::*; + +/// The result of [`SlotWorker::on_slot`]. +#[derive(Debug, Clone, Encode, From)] +pub struct SlotResult { + /// The result of a slot operation. + pub block: SignedSidechainBlock, + /// Parentchain state transitions triggered by sidechain state transitions. + /// + /// Any sidechain stf that invokes a parentchain stf must not commit its state change + /// before the parentchain effect has been finalized. + pub parentchain_effects: Vec, +} + +/// A worker that should be invoked at every new slot for a specific shard. +/// +/// The implementation should not make any assumptions of the slot being bound to the time or +/// similar. The only valid assumption is that the slot number is always increasing. +pub trait SlotWorker { + /// Output generated after a slot + type Output: SignedSidechainBlockTrait + Send + 'static; + + /// Called when a new slot is triggered. + /// + /// Returns a [`SlotResult`] iff a block was successfully built in + /// the slot. Otherwise `None` is returned. + fn on_slot( + &mut self, + slot_info: SlotInfo, + shard: ShardIdentifierFor, + ) -> Option>; +} + +/// A slot worker scheduler that should be invoked at every new slot. +/// +/// It manages the timeslots of individual per shard `SlotWorker`s. It gives each shard an equal +/// amount of time to produce it's result, equally distributing leftover time from a previous shard's +/// slot share to all subsequent slots. +pub trait PerShardSlotWorkerScheduler { + /// Output generated after a slot + type Output: Send + 'static; + + /// The shard type 'PerShardWorker's operate on. + type ShardIdentifier: Send + 'static + Debug + Clone; + + /// Called when a new slot is triggered. + /// + /// Returns a [`SlotResult`] iff a block was successfully built in + /// the slot. Otherwise `None` is returned. + fn on_slot( + &mut self, + slot_info: SlotInfo, + shard: Vec, + ) -> Self::Output; +} + +/// A skeleton implementation for `SlotWorker` which tries to claim a slot at +/// its beginning and tries to produce a block if successfully claimed, timing +/// out if block production takes too long. +pub trait SimpleSlotWorker { + /// The type of proposer to use to build blocks. + type Proposer: Proposer; + + /// Data associated with a slot claim. + type Claim: Send + 'static; + + /// Epoch data necessary for authoring. + type EpochData: Send + 'static; + + /// Output generated after a slot + type Output: SignedSidechainBlockTrait + Send + 'static; + + /// The logging target to use when logging messages. + fn logging_target(&self) -> &'static str; + + /// Returns the epoch data necessary for authoring. For time-dependent epochs, + /// use the provided slot number as a canonical source of time. + fn epoch_data( + &self, + header: &ParentchainBlock::Header, + slot: Slot, + ) -> Result; + + /// Returns the number of authorities given the epoch data. + /// None indicate that the authorities information is incomplete. + fn authorities_len(&self, epoch_data: &Self::EpochData) -> Option; + + /// Tries to claim the given slot, returning an object with claim data if successful. + fn claim_slot( + &self, + header: &ParentchainBlock::Header, + slot: Slot, + epoch_data: &Self::EpochData, + ) -> Option; + + /// Creates the proposer for the current slot + fn proposer( + &mut self, + header: ParentchainBlock::Header, + shard: ShardIdentifierFor, + ) -> Result; + + /// Remaining duration for proposing. + fn proposing_remaining_duration(&self, slot_info: &SlotInfo) -> Duration; + + /// Trigger the import of the given parentchain block. + /// + /// Returns the header of the latest imported block. In case no block was imported with this trigger, + /// None is returned. + fn import_parentchain_blocks_until( + &self, + last_imported_parentchain_header: &::Hash, + ) -> Result, ConsensusError>; + + /// Peek the parentchain import queue for the latest block in queue. + /// Does not perform the import or mutate the queue. + fn peek_latest_parentchain_header( + &self, + ) -> Result, ConsensusError>; + + /// Implements [`SlotWorker::on_slot`]. This is an adaption from + /// substrate's sc-consensus-slots implementation. There, the slot worker handles all the + /// scheduling itself. Unfortunately, we can't use the same principle in the enclave due to some + /// futures-primitives not being available in sgx, e.g. `Delay` in our case. Hence, before + /// reimplementing the those things ourselves, we take a simplified approach and simply call + /// this function from the outside at each slot. + fn on_slot( + &mut self, + slot_info: SlotInfo, + shard: ShardIdentifierFor, + ) -> Option> { + let (_timestamp, slot) = (slot_info.timestamp, slot_info.slot); + let logging_target = self.logging_target(); + + let remaining_duration = self.proposing_remaining_duration(&slot_info); + + if remaining_duration == Duration::default() { + debug!( + target: logging_target, + "Skipping proposal slot {} since there's no time left to propose", *slot, + ); + + return None + } + + let latest_parentchain_header = match self.peek_latest_parentchain_header() { + Ok(Some(peeked_header)) => peeked_header, + Ok(None) => slot_info.last_imported_parentchain_head.clone(), + Err(e) => { + warn!( + target: logging_target, + "Failed to peek latest parentchain block header: {:?}", e + ); + return None + }, + }; + + let epoch_data = match self.epoch_data(&latest_parentchain_header, slot) { + Ok(epoch_data) => epoch_data, + Err(e) => { + warn!( + target: logging_target, + "Unable to fetch epoch data at block {:?}: {:?}", + latest_parentchain_header.hash(), + e, + ); + + return None + }, + }; + + let authorities_len = self.authorities_len(&epoch_data); + + if !authorities_len.map(|a| a > 0).unwrap_or(false) { + debug!( + target: logging_target, + "Skipping proposal slot. Authorities len {:?}", authorities_len + ); + } + + let _claim = self.claim_slot(&latest_parentchain_header, slot, &epoch_data)?; + + // Import the peeked parentchain header(s). + let last_imported_header = + match self.import_parentchain_blocks_until(&latest_parentchain_header.hash()) { + Ok(h) => h, + Err(e) => { + warn!( + target: logging_target, + "Failed to import and retrieve parentchain block header: {:?}", e + ); + return None + }, + }; + + let proposer = match self.proposer(latest_parentchain_header.clone(), shard) { + Ok(p) => p, + Err(e) => { + warn!(target: logging_target, "Could not create proposer: {:?}", e); + return None + }, + }; + + let proposing = match proposer.propose(remaining_duration) { + Ok(p) => p, + Err(e) => { + warn!(target: logging_target, "Could not propose: {:?}", e); + return None + }, + }; + + if !timestamp_within_slot(&slot_info, &proposing.block) { + warn!( + target: logging_target, + "⌛️ Discarding proposal for slot {}, block number {}; block production took too long", + *slot, proposing.block.block().header().block_number(), + ); + + return None + } + + if last_imported_header.is_some() { + println!( + "Syncing Parentchain block number {:?} at Sidechain block number {:?} ", + latest_parentchain_header.number(), + proposing.block.block().header().block_number() + ); + } + + info!("Proposing sidechain block (number: {}, hash: {}) based on parentchain block (number: {:?}, hash: {:?})", + proposing.block.block().header().block_number(), proposing.block.hash(), + latest_parentchain_header.number(), latest_parentchain_header.hash() + ); + + Some(SlotResult { + block: proposing.block, + parentchain_effects: proposing.parentchain_effects, + }) + } +} + +impl + Send> + SlotWorker for T +{ + type Output = T::Output; + + fn on_slot( + &mut self, + slot_info: SlotInfo, + shard: ShardIdentifierFor, + ) -> Option> { + SimpleSlotWorker::on_slot(self, slot_info, shard) + } +} + +impl> + PerShardSlotWorkerScheduler for T +{ + type Output = Vec>; + + type ShardIdentifier = ShardIdentifierFor; + + fn on_slot( + &mut self, + slot_info: SlotInfo, + shards: Vec, + ) -> Self::Output { + let logging_target = SimpleSlotWorker::logging_target(self); + + let mut remaining_shards = shards.len(); + let mut slot_results = Vec::with_capacity(remaining_shards); + + for shard in shards.into_iter() { + let now = duration_now(); // It's important we have a common `now` for all following computations. + let shard_remaining_duration = duration_difference(now, slot_info.ends_at) + .and_then(|time| time.checked_div(remaining_shards as u32)) + .unwrap_or_default(); + + // important to check against millis here. We had the corner-case in production + // setup where `shard_remaining_duration` contained only nanos. + if shard_remaining_duration.as_millis() == u128::default() { + info!( + target: logging_target, + "⌛️ Could not produce blocks for all shards; block production took too long", + ); + + return slot_results + } + + let shard_slot_ends_at = now + shard_remaining_duration; + let shard_slot = SlotInfo::new( + slot_info.slot, + now, + shard_remaining_duration, + shard_slot_ends_at, + slot_info.last_imported_parentchain_head.clone(), + ); + + match SimpleSlotWorker::on_slot(self, shard_slot, shard) { + Some(res) => slot_results.push(res), + None => info!( + target: logging_target, + "Did not produce a block for slot {} in shard {:?}", *slot_info.slot, shard + ), + } + + remaining_shards -= 1; + } + + slot_results + } +} diff --git a/tee-worker/sidechain/consensus/slots/src/mocks.rs b/tee-worker/sidechain/consensus/slots/src/mocks.rs new file mode 100644 index 0000000000..ea6e4d4d6c --- /dev/null +++ b/tee-worker/sidechain/consensus/slots/src/mocks.rs @@ -0,0 +1,116 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +use crate::{slots::Slot, SimpleSlotWorker, SlotInfo, SlotResult}; +use its_consensus_common::{Proposal, Proposer, Result}; +use its_primitives::{traits::ShardIdentifierFor, types::SignedBlock as SignedSidechainBlock}; +use sp_runtime::traits::{Block as ParentchainBlockTrait, Header as ParentchainHeaderTrait}; +use std::{marker::PhantomData, thread, time::Duration}; + +#[derive(Default)] +pub(crate) struct ProposerMock { + _phantom: PhantomData, +} + +impl Proposer for ProposerMock +where + B: ParentchainBlockTrait, +{ + fn propose(&self, _max_duration: Duration) -> Result> { + todo!() + } +} + +#[derive(Default)] +pub(crate) struct SimpleSlotWorkerMock +where + B: ParentchainBlockTrait, +{ + pub slot_infos: Vec>, + pub slot_time_spent: Option, +} + +impl SimpleSlotWorker for SimpleSlotWorkerMock +where + B: ParentchainBlockTrait, +{ + type Proposer = ProposerMock; + + type Claim = u64; + + type EpochData = u64; + + type Output = SignedSidechainBlock; + + fn logging_target(&self) -> &'static str { + "test" + } + + fn epoch_data(&self, _header: &B::Header, _slot: Slot) -> Result { + todo!() + } + + fn authorities_len(&self, _epoch_data: &Self::EpochData) -> Option { + todo!() + } + + fn claim_slot( + &self, + _header: &B::Header, + _slot: Slot, + _epoch_data: &Self::EpochData, + ) -> Option { + todo!() + } + + fn proposer( + &mut self, + _header: B::Header, + _shard: ShardIdentifierFor, + ) -> Result { + todo!() + } + + fn proposing_remaining_duration(&self, _slot_info: &SlotInfo) -> Duration { + todo!() + } + + fn import_parentchain_blocks_until( + &self, + _last_imported_parentchain_header: &::Hash, + ) -> Result> { + todo!() + } + + fn peek_latest_parentchain_header(&self) -> Result> { + todo!() + } + + fn on_slot( + &mut self, + slot_info: SlotInfo, + _shard: ShardIdentifierFor, + ) -> Option> { + self.slot_infos.push(slot_info); + + if let Some(sleep_duration) = self.slot_time_spent { + thread::sleep(sleep_duration); + } + + None + } +} diff --git a/tee-worker/sidechain/consensus/slots/src/per_shard_slot_worker_tests.rs b/tee-worker/sidechain/consensus/slots/src/per_shard_slot_worker_tests.rs new file mode 100644 index 0000000000..a043277801 --- /dev/null +++ b/tee-worker/sidechain/consensus/slots/src/per_shard_slot_worker_tests.rs @@ -0,0 +1,90 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +use crate::{mocks::SimpleSlotWorkerMock, PerShardSlotWorkerScheduler, SlotInfo}; +use itc_parentchain_test::parentchain_header_builder::ParentchainHeaderBuilder; +use itp_settings::sidechain::SLOT_DURATION; +use itp_time_utils::duration_now; +use itp_types::{Block as ParentchainBlock, ShardIdentifier}; +use its_block_verification::slot::slot_from_timestamp_and_duration; + +type TestSlotWorker = SimpleSlotWorkerMock; + +#[test] +fn slot_timings_are_correct_with_multiple_shards() { + let slot_info = slot_info_from_now(); + let mut slot_worker = + TestSlotWorker { slot_infos: Vec::new(), slot_time_spent: Some(SLOT_DURATION / 10) }; + + let shards = + vec![ShardIdentifier::default(), ShardIdentifier::default(), ShardIdentifier::default()]; + + let _slot_results = + PerShardSlotWorkerScheduler::on_slot(&mut slot_worker, slot_info.clone(), shards.clone()); + + assert_eq!(slot_worker.slot_infos.len(), shards.len()); + + // end-time of the first shard slot should not exceed timestamp + 1/(n_shards) of the total slot duration + let first_shard_slot_end_time = slot_worker.slot_infos.first().unwrap().ends_at.as_millis(); + let expected_upper_bound = (slot_info.timestamp.as_millis() + + SLOT_DURATION.as_millis().checked_div(shards.len() as u128).unwrap()) + + 2u128; + assert!( + first_shard_slot_end_time <= expected_upper_bound, + "First shard end time, expected: {}, actual: {}", + expected_upper_bound, + first_shard_slot_end_time + ); + + // none of the shard slot end times should exceed the global slot end time + for shard_slot_info in slot_worker.slot_infos { + assert!( + shard_slot_info.ends_at.as_millis() <= slot_info.ends_at.as_millis(), + "shard slot info ends at: {} ms, total slot info ends at: {} ms", + shard_slot_info.ends_at.as_millis(), + slot_info.ends_at.as_millis() + ); + } +} + +#[test] +fn if_shard_takes_up_all_slot_time_subsequent_shards_are_not_served() { + let slot_info = slot_info_from_now(); + let mut slot_worker = + TestSlotWorker { slot_infos: Vec::new(), slot_time_spent: Some(SLOT_DURATION) }; + + let shards = + vec![ShardIdentifier::default(), ShardIdentifier::default(), ShardIdentifier::default()]; + + let _slot_results = + PerShardSlotWorkerScheduler::on_slot(&mut slot_worker, slot_info.clone(), shards.clone()); + + assert_eq!(1, slot_worker.slot_infos.len()); +} + +fn slot_info_from_now() -> SlotInfo { + let timestamp_now = duration_now(); + let slot = slot_from_timestamp_and_duration(timestamp_now, SLOT_DURATION); + let slot_ends_at = timestamp_now + SLOT_DURATION; + SlotInfo::new( + slot, + timestamp_now, + SLOT_DURATION, + slot_ends_at, + ParentchainHeaderBuilder::default().build(), + ) +} diff --git a/tee-worker/sidechain/consensus/slots/src/slot_stream.rs b/tee-worker/sidechain/consensus/slots/src/slot_stream.rs new file mode 100644 index 0000000000..1c738419bf --- /dev/null +++ b/tee-worker/sidechain/consensus/slots/src/slot_stream.rs @@ -0,0 +1,116 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +//! Slots functionality for Substrate. +//! +//! Some consensus algorithms have a concept of *slots*, which are intervals in +//! time during which certain events can and/or must occur. This crate +//! provides generic functionality for slots. + +use crate::time_until_next_slot; +use futures_timer::Delay; +use std::time::Duration; + +/// Executes given `task` repeatedly when the next slot becomes available. +pub async fn start_slot_worker(task: F, slot_duration: Duration) +where + F: Fn(), +{ + let mut slot_stream = SlotStream::new(slot_duration); + + loop { + slot_stream.next_slot().await; + task(); + } +} + +/// Stream to calculate the slot schedule with. +pub struct SlotStream { + slot_duration: Duration, + inner_delay: Option, +} + +impl SlotStream { + pub fn new(slot_duration: Duration) -> Self { + SlotStream { slot_duration, inner_delay: None } + } +} + +impl SlotStream { + /// Waits for the duration of `inner_delay`. + /// Upon timeout, `inner_delay` is reset according to the time left until next slot. + pub async fn next_slot(&mut self) { + self.inner_delay = match self.inner_delay.take() { + None => { + // Delay is not initialized in this case, + // so we have to initialize with the time until the next slot. + let wait_dur = time_until_next_slot(self.slot_duration); + Some(Delay::new(wait_dur)) + }, + Some(d) => Some(d), + }; + + if let Some(inner_delay) = self.inner_delay.take() { + inner_delay.await; + } + + let ends_in = time_until_next_slot(self.slot_duration); + + // Re-schedule delay for next slot. + self.inner_delay = Some(Delay::new(ends_in)); + } +} + +#[cfg(test)] +mod tests { + use super::*; + use std::{thread, time::Instant}; + + const SLOT_DURATION: Duration = Duration::from_millis(300); + const SLOT_TOLERANCE: Duration = Duration::from_millis(10); + + #[tokio::test] + async fn short_task_execution_does_not_influence_next_slot() { + let mut slot_stream = SlotStream::new(SLOT_DURATION); + + slot_stream.next_slot().await; + let now = Instant::now(); + // Task execution is shorter than slot duration. + thread::sleep(Duration::from_millis(200)); + slot_stream.next_slot().await; + + let elapsed = now.elapsed(); + assert!(elapsed >= SLOT_DURATION - SLOT_TOLERANCE); + assert!(elapsed <= SLOT_DURATION + SLOT_TOLERANCE); + } + + #[tokio::test] + async fn long_task_execution_does_not_cause_drift() { + let mut slot_stream = SlotStream::new(SLOT_DURATION); + + slot_stream.next_slot().await; + let now = Instant::now(); + // Task execution is longer than slot duration. + thread::sleep(Duration::from_millis(500)); + slot_stream.next_slot().await; + slot_stream.next_slot().await; + + let elapsed = now.elapsed(); + assert!(elapsed >= 2 * SLOT_DURATION - SLOT_TOLERANCE); + assert!(elapsed <= 2 * SLOT_DURATION + SLOT_TOLERANCE); + } +} diff --git a/tee-worker/sidechain/consensus/slots/src/slots.rs b/tee-worker/sidechain/consensus/slots/src/slots.rs new file mode 100644 index 0000000000..4e14eed784 --- /dev/null +++ b/tee-worker/sidechain/consensus/slots/src/slots.rs @@ -0,0 +1,418 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +//! Utility stream for yielding slots in a loop. +//! +//! This is used instead of `futures_timer::Interval` because it was unreliable. + +pub use sp_consensus_slots::Slot; + +use itp_sgx_io::StaticSealedIO; +use itp_time_utils::duration_now; +use its_block_verification::slot::slot_from_timestamp_and_duration; +use its_consensus_common::Error as ConsensusError; +use its_primitives::traits::{ + Block as SidechainBlockTrait, BlockData, SignedBlock as SignedSidechainBlockTrait, +}; +use log::warn; +use sp_runtime::traits::Block as ParentchainBlockTrait; +use std::time::Duration; + +/// Returns the duration until the next slot from now. +pub fn time_until_next_slot(slot_duration: Duration) -> Duration { + let now = duration_now().as_millis(); + + if slot_duration.as_millis() == u128::default() { + log::warn!("[Slots]: slot_duration.as_millis() is 0"); + return Default::default() + } + + let next_slot = (now + slot_duration.as_millis()) / slot_duration.as_millis(); + let remaining_millis = next_slot * slot_duration.as_millis() - now; + Duration::from_millis(remaining_millis as u64) +} + +/// Information about a slot. +#[derive(Debug, Clone)] +pub struct SlotInfo { + /// The slot number as found in the inherent data. + pub slot: Slot, + /// Current timestamp as found in the inherent data. + pub timestamp: Duration, + /// Slot duration. + pub duration: Duration, + /// The time at which the slot ends. + pub ends_at: Duration, + /// Last imported parentchain header, potentially outdated. + pub last_imported_parentchain_head: ParentchainBlock::Header, +} + +impl SlotInfo { + /// Create a new [`SlotInfo`]. + /// + /// `ends_at` is calculated using `now` and `time_until_next_slot`. + pub fn new( + slot: Slot, + timestamp: Duration, + duration: Duration, + ends_at: Duration, + parentchain_head: ParentchainBlock::Header, + ) -> Self { + Self { + slot, + timestamp, + duration, + ends_at, + last_imported_parentchain_head: parentchain_head, + } + } + + pub fn duration_remaining(&self) -> Option { + let duration_now = duration_now(); + if self.ends_at <= duration_now { + return None + } + Some(self.ends_at - duration_now) + } +} + +/// The time at which the slot ends. +/// +/// !! Slot duration needs to be the 'global' slot duration that is used for the sidechain. +/// Do not use this with 'custom' slot durations, as used e.g. for the shard slots. +pub fn slot_ends_at(slot: Slot, slot_duration: Duration) -> Duration { + Duration::from_millis(*slot.saturating_add(1u64) * (slot_duration.as_millis() as u64)) +} + +pub(crate) fn timestamp_within_slot< + ParentchainBlock: ParentchainBlockTrait, + SignedSidechainBlock: SignedSidechainBlockTrait, +>( + slot: &SlotInfo, + proposal: &SignedSidechainBlock, +) -> bool { + let proposal_stamp = proposal.block().block_data().timestamp(); + + let is_within_slot = slot.timestamp.as_millis() as u64 <= proposal_stamp + && slot.ends_at.as_millis() as u64 >= proposal_stamp; + + if !is_within_slot { + warn!( + "Proposed block slot time: {} ms, slot start: {} ms , slot end: {} ms", + proposal_stamp, + slot.timestamp.as_millis(), + slot.ends_at.as_millis() + ); + } + + is_within_slot +} + +pub fn yield_next_slot( + timestamp: Duration, + duration: Duration, + header: ParentchainBlock::Header, + last_slot_getter: &mut SlotGetter, +) -> Result>, ConsensusError> +where + SlotGetter: GetLastSlot, + ParentchainBlock: ParentchainBlockTrait, +{ + if duration == Default::default() { + return Err(ConsensusError::Other("Tried to yield next slot with 0 duration".into())) + } + + let last_slot = last_slot_getter.get_last_slot()?; + let slot = slot_from_timestamp_and_duration(timestamp, duration); + + if slot <= last_slot { + return Ok(None) + } + + last_slot_getter.set_last_slot(slot)?; + + let slot_ends_time = slot_ends_at(slot, duration); + Ok(Some(SlotInfo::new(slot, timestamp, duration, slot_ends_time, header))) +} + +pub trait GetLastSlot { + fn get_last_slot(&self) -> Result; + fn set_last_slot(&mut self, slot: Slot) -> Result<(), ConsensusError>; +} + +impl> GetLastSlot for T { + fn get_last_slot(&self) -> Result { + T::unseal_from_static_file() + } + fn set_last_slot(&mut self, slot: Slot) -> Result<(), ConsensusError> { + T::seal_to_static_file(&slot) + } +} + +#[cfg(all(not(feature = "std"), feature = "sgx"))] +pub mod sgx { + use super::*; + use codec::{Decode, Encode}; + use itp_settings::files::LAST_SLOT_BIN; + use itp_sgx_io::{seal, unseal, StaticSealedIO}; + use lazy_static::lazy_static; + use std::sync::SgxRwLock; + + pub struct LastSlotSeal; + + lazy_static! { + static ref FILE_LOCK: SgxRwLock<()> = Default::default(); + } + + impl StaticSealedIO for LastSlotSeal { + type Error = ConsensusError; + type Unsealed = Slot; + + fn unseal_from_static_file() -> Result { + let _ = FILE_LOCK.read().map_err(|e| Self::Error::Other(format!("{:?}", e).into()))?; + + match unseal(LAST_SLOT_BIN) { + Ok(slot) => Ok(Decode::decode(&mut slot.as_slice())?), + Err(_) => { + log::info!("Could not open {:?} file, returning first slot", LAST_SLOT_BIN); + Ok(Default::default()) + }, + } + } + + fn seal_to_static_file(unsealed: &Self::Unsealed) -> Result<(), Self::Error> { + let _ = FILE_LOCK.write().map_err(|e| Self::Error::Other(format!("{:?}", e).into()))?; + Ok(unsealed.using_encoded(|bytes| seal(bytes, LAST_SLOT_BIN))?) + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use core::assert_matches::assert_matches; + use itc_parentchain_test::parentchain_header_builder::ParentchainHeaderBuilder; + use itp_sgx_io::StaticSealedIO; + use itp_types::Block as ParentchainBlock; + use its_primitives::{ + traits::{Block as BlockT, SignBlock}, + types::block::{Block, SignedBlock}, + }; + use its_test::{ + sidechain_block_data_builder::SidechainBlockDataBuilder, + sidechain_header_builder::SidechainHeaderBuilder, + }; + use sp_keyring::ed25519::Keyring; + use std::{fmt::Debug, thread, time::SystemTime}; + + const SLOT_DURATION: Duration = Duration::from_millis(1000); + const ALLOWED_THRESHOLD: Duration = Duration::from_millis(1); + + struct LastSlotSealMock; + + impl StaticSealedIO for LastSlotSealMock { + type Error = ConsensusError; + type Unsealed = Slot; + + fn unseal_from_static_file() -> Result { + Ok(slot_from_timestamp_and_duration(duration_now(), SLOT_DURATION)) + } + + fn seal_to_static_file(_unsealed: &Self::Unsealed) -> Result<(), Self::Error> { + println!("Seal method stub called."); + Ok(()) + } + } + + fn test_block_with_time_stamp(timestamp: u64) -> SignedBlock { + let header = SidechainHeaderBuilder::default().build(); + + let block_data = SidechainBlockDataBuilder::default().with_timestamp(timestamp).build(); + + Block::new(header, block_data).sign_block(&Keyring::Alice.pair()) + } + + fn slot(slot: u64) -> SlotInfo { + SlotInfo { + slot: slot.into(), + timestamp: duration_now(), + duration: SLOT_DURATION, + ends_at: duration_now() + SLOT_DURATION, + last_imported_parentchain_head: ParentchainHeaderBuilder::default().build(), + } + } + + fn timestamp_in_the_future(later: Duration) -> u64 { + let moment = SystemTime::now() + later; + let dur = moment.duration_since(SystemTime::UNIX_EPOCH).unwrap_or_else(|e| { + panic!("Current time {:?} is before unix epoch. Something is wrong: {:?}", moment, e) + }); + dur.as_millis() as u64 + } + + fn timestamp_in_the_past(earlier: Duration) -> u64 { + let moment = SystemTime::now() - earlier; + let dur = moment.duration_since(SystemTime::UNIX_EPOCH).unwrap_or_else(|e| { + panic!("Current time {:?} is before unix epoch. Something is wrong: {:?}", moment, e) + }); + dur.as_millis() as u64 + } + + fn assert_consensus_other_err(result: Result, msg: &str) { + assert_matches!(result.unwrap_err(), ConsensusError::Other( + m, + ) if m.to_string() == msg) + } + + #[test] + fn time_until_next_slot_returns_default_on_nano_duration() { + // prevent panic: https://github.com/integritee-network/worker/issues/439 + assert_eq!(time_until_next_slot(Duration::from_nanos(999)), Default::default()) + } + + #[test] + fn slot_info_ends_at_does_not_change_after_second_calculation() { + let timestamp = duration_now(); + let pc_header = ParentchainHeaderBuilder::default().build(); + let slot: Slot = 1000.into(); + + let slot_end_time = slot_ends_at(slot, SLOT_DURATION); + let slot_one: SlotInfo = + SlotInfo::new(slot, timestamp, SLOT_DURATION, slot_end_time, pc_header.clone()); + thread::sleep(Duration::from_millis(200)); + let slot_two: SlotInfo = + SlotInfo::new(slot, timestamp, SLOT_DURATION, slot_end_time, pc_header); + + let difference_of_ends_at = + (slot_one.ends_at.as_millis()).abs_diff(slot_two.ends_at.as_millis()); + + assert!( + difference_of_ends_at < ALLOWED_THRESHOLD.as_millis(), + "Diff in ends at timestamp: {} ms, tolerance: {} ms", + difference_of_ends_at, + ALLOWED_THRESHOLD.as_millis() + ); + } + + #[test] + fn duration_remaing_returns_none_if_ends_at_is_in_the_past() { + let slot: SlotInfo = SlotInfo { + slot: 1.into(), + timestamp: duration_now() - Duration::from_secs(5), + duration: SLOT_DURATION, + ends_at: duration_now() + SLOT_DURATION - Duration::from_secs(5), + last_imported_parentchain_head: ParentchainHeaderBuilder::default().build(), + }; + assert!(slot.duration_remaining().is_none()); + } + + #[test] + fn duration_remaining_returns_some_if_ends_at_is_in_the_future() { + let slot: SlotInfo = SlotInfo { + slot: 1.into(), + timestamp: duration_now() - Duration::from_secs(5), + duration: SLOT_DURATION, + ends_at: duration_now() + Duration::from_secs(60), + last_imported_parentchain_head: ParentchainHeaderBuilder::default().build(), + }; + let maybe_duration_remaining = slot.duration_remaining(); + assert!(maybe_duration_remaining.is_some()); + assert!(maybe_duration_remaining.unwrap() > Duration::from_secs(30)); + } + + #[test] + fn slot_info_ends_at_does_is_correct_even_if_delay_is_more_than_slot_duration() { + let timestamp = duration_now(); + let pc_header = ParentchainHeaderBuilder::default().build(); + let slot: Slot = 1000.into(); + let slot_end_time = slot_ends_at(slot, SLOT_DURATION); + + thread::sleep(SLOT_DURATION * 2); + let slot: SlotInfo = + SlotInfo::new(slot, timestamp, SLOT_DURATION, slot_end_time, pc_header); + + assert!(slot.ends_at < duration_now()); + } + + #[test] + fn timestamp_within_slot_returns_true_for_correct_timestamp() { + let slot = slot(1); + let time_stamp_in_slot = timestamp_in_the_future(SLOT_DURATION / 2); + + let block = test_block_with_time_stamp(time_stamp_in_slot); + + assert!(timestamp_within_slot(&slot, &block)); + } + + #[test] + fn timestamp_within_slot_returns_false_if_timestamp_after_slot() { + let slot = slot(1); + let time_stamp_after_slot = + timestamp_in_the_future(SLOT_DURATION + Duration::from_millis(10)); + + let block_too_late = test_block_with_time_stamp(time_stamp_after_slot); + + assert!(!timestamp_within_slot(&slot, &block_too_late)); + } + + #[test] + fn timestamp_within_slot_returns_false_if_timestamp_before_slot() { + let slot = slot(1); + let time_stamp_before_slot = timestamp_in_the_past(Duration::from_millis(10)); + + let block_too_early = test_block_with_time_stamp(time_stamp_before_slot); + + assert!(!timestamp_within_slot(&slot, &block_too_early)); + } + + #[test] + fn yield_next_slot_returns_none_when_slot_equals_last_slot() { + assert!(yield_next_slot::<_, ParentchainBlock>( + duration_now(), + SLOT_DURATION, + ParentchainHeaderBuilder::default().build(), + &mut LastSlotSealMock, + ) + .unwrap() + .is_none()) + } + + #[test] + fn yield_next_slot_returns_next_slot() { + assert!(yield_next_slot::<_, ParentchainBlock>( + duration_now() + SLOT_DURATION, + SLOT_DURATION, + ParentchainHeaderBuilder::default().build(), + &mut LastSlotSealMock + ) + .unwrap() + .is_some()) + } + + #[test] + fn yield_next_slot_returns_err_on_0_duration() { + assert_consensus_other_err( + yield_next_slot::<_, ParentchainBlock>( + duration_now(), + Default::default(), + ParentchainHeaderBuilder::default().build(), + &mut LastSlotSealMock, + ), + "Tried to yield next slot with 0 duration", + ) + } +} diff --git a/tee-worker/sidechain/peer-fetch/Cargo.toml b/tee-worker/sidechain/peer-fetch/Cargo.toml new file mode 100644 index 0000000000..59e9ffb933 --- /dev/null +++ b/tee-worker/sidechain/peer-fetch/Cargo.toml @@ -0,0 +1,36 @@ +[package] +authors = ["Integritee AG "] +edition = "2021" +name = "its-peer-fetch" +version = "0.9.0" + +[dependencies] +# crates.io +async-trait = { version = "0.1.50" } +jsonrpsee = { version = "0.2.0", features = ["client", "ws-server", "macros"] } +log = { version = "0.4" } +serde = "1.0" +serde_json = "1.0" +thiserror = { version = "1.0" } +tokio = { version = "1.6.1", features = ["full"] } + +# local +itc-rpc-client = { path = "../../core/rpc-client" } +itp-node-api = { path = "../../core-primitives/node-api" } +its-primitives = { path = "../primitives" } +its-rpc-handler = { path = "../rpc-handler" } +its-storage = { path = "../storage" } + +[dev-dependencies] +# crates.io +anyhow = "1.0.40" +# local +itp-node-api = { path = "../../core-primitives/node-api", features = ["mocks"] } +itp-test = { path = "../../core-primitives/test" } +its-storage = { path = "../storage", features = ["mocks"] } +its-test = { path = "../test" } + +[features] +default = ["std"] +mocks = [] +std = [] diff --git a/tee-worker/sidechain/peer-fetch/src/block_fetch_client.rs b/tee-worker/sidechain/peer-fetch/src/block_fetch_client.rs new file mode 100644 index 0000000000..4077f95908 --- /dev/null +++ b/tee-worker/sidechain/peer-fetch/src/block_fetch_client.rs @@ -0,0 +1,141 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +use crate::{error::Result, untrusted_peer_fetch::FetchUntrustedPeers, FetchBlocksFromPeer}; +use async_trait::async_trait; +use its_primitives::{ + traits::SignedBlock as SignedBlockTrait, + types::{BlockHash, ShardIdentifier}, +}; +use its_rpc_handler::constants::RPC_METHOD_NAME_FETCH_BLOCKS_FROM_PEER; +use jsonrpsee::{ + types::to_json_value, + ws_client::{traits::Client, WsClientBuilder}, +}; +use log::info; +use serde::de::DeserializeOwned; +use std::marker::PhantomData; + +/// Sidechain block fetcher implementation. +/// +/// Fetches block from a peer with an RPC request. +pub struct BlockFetcher { + peer_fetcher: PeerFetcher, + _phantom: PhantomData, +} + +impl BlockFetcher +where + SignedBlock: SignedBlockTrait + DeserializeOwned, + PeerFetcher: FetchUntrustedPeers + Send + Sync, +{ + pub fn new(peer_fetcher: PeerFetcher) -> Self { + BlockFetcher { peer_fetcher, _phantom: Default::default() } + } +} + +#[async_trait] +impl FetchBlocksFromPeer for BlockFetcher +where + SignedBlock: SignedBlockTrait + DeserializeOwned, + PeerFetcher: FetchUntrustedPeers + Send + Sync, +{ + type SignedBlockType = SignedBlock; + + async fn fetch_blocks_from_peer( + &self, + last_imported_block_hash: BlockHash, + maybe_until_block_hash: Option, + shard_identifier: ShardIdentifier, + ) -> Result> { + let sync_source_rpc_url = + self.peer_fetcher.get_untrusted_peer_url_of_shard(&shard_identifier)?; + + let rpc_parameters = vec![to_json_value(( + last_imported_block_hash, + maybe_until_block_hash, + shard_identifier, + ))?]; + + info!("Got untrusted url for peer block fetching: {}", sync_source_rpc_url); + + let client = WsClientBuilder::default().build(sync_source_rpc_url.as_str()).await?; + + info!("Sending fetch blocks from peer request"); + + client + .request::>( + RPC_METHOD_NAME_FETCH_BLOCKS_FROM_PEER, + rpc_parameters.into(), + ) + .await + .map_err(|e| e.into()) + } +} + +#[cfg(test)] +mod tests { + + use super::*; + use crate::{ + block_fetch_server::BlockFetchServerModuleBuilder, + mocks::untrusted_peer_fetch_mock::UntrustedPeerFetcherMock, + }; + use its_primitives::types::block::SignedBlock; + use its_storage::fetch_blocks_mock::FetchBlocksMock; + use its_test::sidechain_block_builder::SidechainBlockBuilder; + use jsonrpsee::ws_server::WsServerBuilder; + use std::{net::SocketAddr, sync::Arc}; + + async fn run_server( + blocks: Vec, + web_socket_url: &str, + ) -> anyhow::Result { + let mut server = WsServerBuilder::default().build(web_socket_url).await?; + + let storage_block_fetcher = Arc::new(FetchBlocksMock::default().with_blocks(blocks)); + let module = BlockFetchServerModuleBuilder::new(storage_block_fetcher).build().unwrap(); + + server.register_module(module).unwrap(); + + let socket_addr = server.local_addr()?; + tokio::spawn(async move { server.start().await }); + Ok(socket_addr) + } + + #[tokio::test] + async fn fetch_blocks_without_bounds_from_peer_works() { + const W1_URL: &str = "127.0.0.1:2233"; + + let blocks_to_fetch = vec![ + SidechainBlockBuilder::random().build_signed(), + SidechainBlockBuilder::random().build_signed(), + ]; + run_server(blocks_to_fetch.clone(), W1_URL).await.unwrap(); + + let peer_fetch_mock = UntrustedPeerFetcherMock::new(format!("ws://{}", W1_URL)); + + let peer_fetcher_client = BlockFetcher::::new(peer_fetch_mock); + + let blocks_fetched = peer_fetcher_client + .fetch_blocks_from_peer(BlockHash::default(), None, ShardIdentifier::default()) + .await + .unwrap(); + + assert_eq!(blocks_to_fetch, blocks_fetched); + } +} diff --git a/tee-worker/sidechain/peer-fetch/src/block_fetch_server.rs b/tee-worker/sidechain/peer-fetch/src/block_fetch_server.rs new file mode 100644 index 0000000000..628e3ba048 --- /dev/null +++ b/tee-worker/sidechain/peer-fetch/src/block_fetch_server.rs @@ -0,0 +1,76 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +use crate::error::Result; +use its_primitives::types::{BlockHash, ShardIdentifier, SignedBlock}; +use its_rpc_handler::constants::RPC_METHOD_NAME_FETCH_BLOCKS_FROM_PEER; +use its_storage::interface::FetchBlocks; +use jsonrpsee::{types::error::CallError, RpcModule}; +use log::*; +use std::sync::Arc; + +/// RPC server module builder for fetching sidechain blocks from peers. +pub struct BlockFetchServerModuleBuilder { + sidechain_block_fetcher: Arc, +} + +impl BlockFetchServerModuleBuilder +where + // Have to use the concrete `SignedBlock` type, because the ShardIdentifier type + // does not have the Serialize/Deserialize trait bound. + FetchBlocksFromStorage: FetchBlocks + Send + Sync + 'static, +{ + pub fn new(sidechain_block_fetcher: Arc) -> Self { + BlockFetchServerModuleBuilder { sidechain_block_fetcher } + } + + pub fn build(self) -> Result>> { + let mut fetch_sidechain_blocks_module = RpcModule::new(self.sidechain_block_fetcher); + fetch_sidechain_blocks_module.register_method( + RPC_METHOD_NAME_FETCH_BLOCKS_FROM_PEER, + |params, sidechain_block_fetcher| { + debug!("{}: {:?}", RPC_METHOD_NAME_FETCH_BLOCKS_FROM_PEER, params); + + let (from_block_hash, maybe_until_block_hash, shard_identifier) = + params.one::<(BlockHash, Option, ShardIdentifier)>()?; + info!("Got request to fetch sidechain blocks from peer. Fetching sidechain blocks from storage \ + (last imported block hash: {:?}, until block hash: {:?}, shard: {}", + from_block_hash, maybe_until_block_hash, shard_identifier); + + match maybe_until_block_hash { + Some(until_block_hash) => sidechain_block_fetcher + .fetch_blocks_in_range( + &from_block_hash, + &until_block_hash, + &shard_identifier, + ) + .map_err(|e| { + error!("Failed to fetch sidechain blocks from storage: {:?}", e); + CallError::Failed(e.into()) + }), + None => sidechain_block_fetcher + .fetch_all_blocks_after(&from_block_hash, &shard_identifier) + .map_err(|e| { + error!("Failed to fetch sidechain blocks from storage: {:?}", e); + CallError::Failed(e.into()) + }), + } + }, + )?; + Ok(fetch_sidechain_blocks_module) + } +} diff --git a/tee-worker/sidechain/peer-fetch/src/error.rs b/tee-worker/sidechain/peer-fetch/src/error.rs new file mode 100644 index 0000000000..8bf1c5f6d2 --- /dev/null +++ b/tee-worker/sidechain/peer-fetch/src/error.rs @@ -0,0 +1,38 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +//! Sidechain peer fetch error. + +pub type Result = core::result::Result; + +#[derive(Debug, thiserror::Error)] +pub enum Error { + #[error("RPC client error: {0}")] + RpcClient(#[from] itc_rpc_client::error::Error), + #[error("Node API extensions error: {0}")] + NodeApiExtensions(#[from] itp_node_api::api_client::ApiClientError), + #[error("Node API factory error: {0}")] + NodeApiFactory(#[from] itp_node_api::node_api_factory::NodeApiFactoryError), + #[error("Serialization error: {0}")] + Serialization(#[from] serde_json::Error), + #[error("JSON RPC error: {0}")] + JsonRpc(#[from] jsonrpsee::types::Error), + #[error("Could not find any peers on-chain for shard: {0:?}")] + NoPeerFoundForShard(its_primitives::types::ShardIdentifier), + #[error(transparent)] + Other(#[from] Box), +} diff --git a/tee-worker/sidechain/peer-fetch/src/lib.rs b/tee-worker/sidechain/peer-fetch/src/lib.rs new file mode 100644 index 0000000000..5af3326970 --- /dev/null +++ b/tee-worker/sidechain/peer-fetch/src/lib.rs @@ -0,0 +1,49 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +#![cfg_attr(not(feature = "std"), no_std)] + +pub mod block_fetch_client; +pub mod block_fetch_server; +pub mod error; +pub mod untrusted_peer_fetch; + +#[cfg(feature = "mocks")] +pub mod mocks; + +use crate::error::Result; +use async_trait::async_trait; +use its_primitives::{ + traits::SignedBlock, + types::{BlockHash, ShardIdentifier}, +}; +use std::vec::Vec; + +/// Trait to fetch block from peer validateers. +/// +/// This is used by an outdated validateer to get the most recent state. +#[async_trait] +pub trait FetchBlocksFromPeer { + type SignedBlockType: SignedBlock; + + async fn fetch_blocks_from_peer( + &self, + last_imported_block_hash: BlockHash, + maybe_until_block_hash: Option, + shard_identifier: ShardIdentifier, + ) -> Result>; +} diff --git a/tee-worker/sidechain/peer-fetch/src/mocks/fetch_blocks_from_peer_mock.rs b/tee-worker/sidechain/peer-fetch/src/mocks/fetch_blocks_from_peer_mock.rs new file mode 100644 index 0000000000..09f9bb92fc --- /dev/null +++ b/tee-worker/sidechain/peer-fetch/src/mocks/fetch_blocks_from_peer_mock.rs @@ -0,0 +1,61 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +use crate::{FetchBlocksFromPeer, Result}; +use async_trait::async_trait; +use its_primitives::{ + traits::SignedBlock as SignedBlockTrait, + types::{BlockHash, ShardIdentifier}, +}; +use std::collections::HashMap; + +pub struct FetchBlocksFromPeerMock { + signed_blocks_map: HashMap>, +} + +impl FetchBlocksFromPeerMock { + pub fn with_signed_blocks( + mut self, + blocks_map: HashMap>, + ) -> Self { + self.signed_blocks_map = blocks_map; + self + } +} + +impl Default for FetchBlocksFromPeerMock { + fn default() -> Self { + FetchBlocksFromPeerMock { signed_blocks_map: HashMap::new() } + } +} + +#[async_trait] +impl FetchBlocksFromPeer for FetchBlocksFromPeerMock +where + SignedBlock: SignedBlockTrait, +{ + type SignedBlockType = SignedBlock; + + async fn fetch_blocks_from_peer( + &self, + _last_imported_block_hash: BlockHash, + _maybe_until_block_hash: Option, + shard_identifier: ShardIdentifier, + ) -> Result> { + Ok(self.signed_blocks_map.get(&shard_identifier).cloned().unwrap_or_default()) + } +} diff --git a/tee-worker/sidechain/peer-fetch/src/mocks/mod.rs b/tee-worker/sidechain/peer-fetch/src/mocks/mod.rs new file mode 100644 index 0000000000..392f8e9b82 --- /dev/null +++ b/tee-worker/sidechain/peer-fetch/src/mocks/mod.rs @@ -0,0 +1,19 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +pub mod fetch_blocks_from_peer_mock; +pub mod untrusted_peer_fetch_mock; diff --git a/tee-worker/sidechain/peer-fetch/src/mocks/untrusted_peer_fetch_mock.rs b/tee-worker/sidechain/peer-fetch/src/mocks/untrusted_peer_fetch_mock.rs new file mode 100644 index 0000000000..8b37b69e00 --- /dev/null +++ b/tee-worker/sidechain/peer-fetch/src/mocks/untrusted_peer_fetch_mock.rs @@ -0,0 +1,35 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +use crate::{error::Result, untrusted_peer_fetch::FetchUntrustedPeers}; +use its_primitives::types::ShardIdentifier; + +pub struct UntrustedPeerFetcherMock { + url: String, +} + +impl UntrustedPeerFetcherMock { + pub fn new(url: String) -> Self { + UntrustedPeerFetcherMock { url } + } +} + +impl FetchUntrustedPeers for UntrustedPeerFetcherMock { + fn get_untrusted_peer_url_of_shard(&self, _shard: &ShardIdentifier) -> Result { + Ok(self.url.clone()) + } +} diff --git a/tee-worker/sidechain/peer-fetch/src/untrusted_peer_fetch.rs b/tee-worker/sidechain/peer-fetch/src/untrusted_peer_fetch.rs new file mode 100644 index 0000000000..7ff9434103 --- /dev/null +++ b/tee-worker/sidechain/peer-fetch/src/untrusted_peer_fetch.rs @@ -0,0 +1,59 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +use crate::error::{Error, Result}; +use itc_rpc_client::direct_client::{DirectApi, DirectClient as DirectWorkerApi}; +use itp_node_api::{api_client::PalletTeerexApi, node_api_factory::CreateNodeApi}; +use its_primitives::types::ShardIdentifier; +use std::sync::Arc; + +/// Trait to fetch untrusted peer servers. +pub trait FetchUntrustedPeers { + fn get_untrusted_peer_url_of_shard(&self, shard: &ShardIdentifier) -> Result; +} + +/// Fetches the untrusted peer servers +/// FIXME: Should probably be combined with the peer fetch in +/// service/src/worker.rs +pub struct UntrustedPeerFetcher { + node_api_factory: Arc, +} + +impl UntrustedPeerFetcher +where + NodeApiFactory: CreateNodeApi + Send + Sync, +{ + pub fn new(node_api: Arc) -> Self { + UntrustedPeerFetcher { node_api_factory: node_api } + } +} + +impl FetchUntrustedPeers for UntrustedPeerFetcher +where + NodeApiFactory: CreateNodeApi + Send + Sync, +{ + fn get_untrusted_peer_url_of_shard(&self, shard: &ShardIdentifier) -> Result { + let node_api = self.node_api_factory.create_api()?; + + let validateer = node_api + .worker_for_shard(shard, None)? + .ok_or_else(|| Error::NoPeerFoundForShard(*shard))?; + + let trusted_worker_client = DirectWorkerApi::new(validateer.url); + Ok(trusted_worker_client.get_untrusted_worker_url()?) + } +} diff --git a/tee-worker/sidechain/primitives/Cargo.toml b/tee-worker/sidechain/primitives/Cargo.toml new file mode 100644 index 0000000000..e181e483fa --- /dev/null +++ b/tee-worker/sidechain/primitives/Cargo.toml @@ -0,0 +1,37 @@ +[package] +authors = ["Integritee AG "] +edition = "2021" +homepage = "https://integritee.network/" +license = "Apache-2.0" +name = "its-primitives" +repository = "https://github.com/integritee-network/pallets/" +version = "0.1.0" + +[dependencies] +codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive", "full"] } +scale-info = { version = "2.0.1", default-features = false, features = ["derive"] } +serde = { version = "1.0.13", default-features = false } + + +# substrate dependencies +sp-core = { default-features = false, git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.29" } +sp-io = { default-features = false, git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.29" } +sp-runtime = { default-features = false, git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.29" } +sp-std = { default-features = false, git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.29" } + + +[features] +default = ["std", "full_crypto"] +full_crypto = [ + "sp-core/full_crypto", +] +std = [ + "codec/std", + "scale-info/std", + "serde/std", + # substrate + "sp-core/std", + "sp-io/std", + "sp-runtime/std", + "sp-std/std", +] diff --git a/tee-worker/sidechain/primitives/src/lib.rs b/tee-worker/sidechain/primitives/src/lib.rs new file mode 100644 index 0000000000..708d9a7942 --- /dev/null +++ b/tee-worker/sidechain/primitives/src/lib.rs @@ -0,0 +1,21 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +#![cfg_attr(not(feature = "std"), no_std)] + +pub mod traits; +pub mod types; diff --git a/tee-worker/sidechain/primitives/src/traits/mod.rs b/tee-worker/sidechain/primitives/src/traits/mod.rs new file mode 100644 index 0000000000..a2fda86449 --- /dev/null +++ b/tee-worker/sidechain/primitives/src/traits/mod.rs @@ -0,0 +1,176 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +//! Some basic abstractions used in sidechain +//! +//! Todo: This crate should be more generic and supply blanket implementations for +//! some generic structs. + +use codec::{Decode, Encode}; +use sp_core::{crypto::Public, H256}; +use sp_runtime::traits::{BlakeTwo256, Hash, Member}; +use sp_std::{fmt::Debug, prelude::*}; + +pub trait Header: Encode + Decode + Clone { + /// Identifier for the shards. + type ShardIdentifier: Encode + Decode + sp_std::hash::Hash + Copy + Member; + + /// Get block number. + fn block_number(&self) -> u64; + /// get parent hash of block + fn parent_hash(&self) -> H256; + /// get shard id of block + fn shard_id(&self) -> Self::ShardIdentifier; + /// get hash of the block's payload + fn block_data_hash(&self) -> H256; + + /// get the `blake2_256` hash of the header. + fn hash(&self) -> H256 { + self.using_encoded(BlakeTwo256::hash) + } + + fn next_finalization_block_number(&self) -> u64; + + fn new( + block_number: u64, + parent_hash: H256, + shard: Self::ShardIdentifier, + block_data_hash: H256, + next_finalization_block_number: u64, + ) -> Self; +} + +pub trait BlockData: Encode + Decode + Send + Sync + Debug + Clone { + /// Public key type of the block author + type Public: Public; + + /// get timestamp of block + fn timestamp(&self) -> u64; + /// get layer one head of block + fn layer_one_head(&self) -> H256; + /// get author of block + fn block_author(&self) -> &Self::Public; + /// get reference of extrinsics of block + fn signed_top_hashes(&self) -> &[H256]; + /// get encrypted payload + fn encrypted_state_diff(&self) -> &Vec; + /// get the `blake2_256` hash of the block + fn hash(&self) -> H256 { + self.using_encoded(BlakeTwo256::hash) + } + + fn new( + author: Self::Public, + layer_one_head: H256, + signed_top_hashes: Vec, + encrypted_payload: Vec, + timestamp: u64, + ) -> Self; +} + +/// Abstraction around a sidechain block. +pub trait Block: Encode + Decode + Send + Sync + Debug + Clone { + /// Sidechain block header type. + type HeaderType: Header; + + /// Sidechain block data type. + type BlockDataType: BlockData; + + /// Public key type of the block author + type Public: Public; + + /// get the `blake2_256` hash of the block + fn hash(&self) -> H256 { + self.header().hash() + } + + /// Get header of the block. + fn header(&self) -> &Self::HeaderType; + + /// Get header of the block. + fn block_data(&self) -> &Self::BlockDataType; + + fn new(header: Self::HeaderType, block_data: Self::BlockDataType) -> Self; +} + +/// ShardIdentifier for a [`SignedBlock`] +pub type ShardIdentifierFor = +<<::Block as Block>::HeaderType as Header>::ShardIdentifier; + +/// A block and it's corresponding signature by the [`Block`] author. +pub trait SignedBlock: Encode + Decode + Send + Sync + Debug + Clone { + /// The block type of the [`SignedBlock`] + type Block: Block; + + /// Public key type of the signer and the block author + type Public: Public; + + /// Signature type of the [`SignedBlock`]'s signature + type Signature; + + /// create a new block instance + fn new(block: Self::Block, signer: Self::Signature) -> Self; + + /// get block reference + fn block(&self) -> &Self::Block; + + /// get signature reference + fn signature(&self) -> &Self::Signature; + + /// get `blake2_256` hash of block + fn hash(&self) -> H256 { + self.block().hash() + } + + /// Verify the signature of a [`Block`] + fn verify_signature(&self) -> bool; +} + +#[cfg(feature = "full_crypto")] +pub use crypto::*; + +#[cfg(feature = "full_crypto")] +mod crypto { + use super::*; + use sp_core::Pair; + + /// Provide signing logic blanket implementations for all block types satisfying the trait bounds. + pub trait SignBlock< + SidechainBlock: Block, + SignedSidechainBlock: SignedBlock, + > + { + fn sign_block(self, signer: &P) -> SignedSidechainBlock + where + ::Signature: From<

::Signature>; + } + + impl SignBlock + for SidechainBlock + where + SidechainBlock: Block, + SignedSidechainBlock: SignedBlock, + { + fn sign_block(self, signer: &P) -> SignedSidechainBlock + where + ::Signature: From<

::Signature>, + { + let signature = self.using_encoded(|b| signer.sign(b)).into(); + SignedSidechainBlock::new(self, signature) + } + } +} diff --git a/tee-worker/sidechain/primitives/src/types/block.rs b/tee-worker/sidechain/primitives/src/types/block.rs new file mode 100644 index 0000000000..8e7902d62d --- /dev/null +++ b/tee-worker/sidechain/primitives/src/types/block.rs @@ -0,0 +1,159 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +use crate::{ + traits::{Block as BlockTrait, SignedBlock as SignedBlockTrait}, + types::{block_data::BlockData, header::SidechainHeader as Header}, +}; +use codec::{Decode, Encode}; +use sp_core::{ed25519, H256}; +use sp_runtime::{traits::Verify, MultiSignature}; + +pub type BlockHash = H256; +pub type BlockNumber = u64; +pub type ShardIdentifier = H256; +pub type Timestamp = u64; + +#[cfg(feature = "std")] +use serde::{Deserialize, Serialize}; + +//FIXME: Should use blocknumber from sgxruntime +// Problem: sgxruntime only with sgx, no std enviornment +// but block.rs should be available in std? +//use sgx_runtime::BlockNumber; + +pub type Signature = MultiSignature; + +/// signed version of block to verify block origin +#[derive(PartialEq, Eq, Clone, Encode, Decode, Debug)] +#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] +pub struct SignedBlock { + /// Plain sidechain block without author signature. + pub block: Block, + /// Block author signature. + pub signature: Signature, +} + +/// Simplified block structure for relay chain submission as an extrinsic. +#[derive(PartialEq, Eq, Clone, Encode, Decode, Debug)] +#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] +pub struct Block { + /// Sidechain Header + pub header: Header, + + /// Sidechain Block data + pub block_data: BlockData, +} + +impl BlockTrait for Block { + type HeaderType = Header; + + type BlockDataType = BlockData; + + type Public = ed25519::Public; + + fn header(&self) -> &Self::HeaderType { + &self.header + } + + fn block_data(&self) -> &Self::BlockDataType { + &self.block_data + } + + fn new(header: Self::HeaderType, block_data: Self::BlockDataType) -> Self { + Self { header, block_data } + } +} + +impl SignedBlockTrait for SignedBlock { + type Block = Block; + + type Public = ed25519::Public; + + type Signature = Signature; + + fn new(block: Self::Block, signature: Self::Signature) -> Self { + Self { block, signature } + } + + /// get block reference + fn block(&self) -> &Self::Block { + &self.block + } + + /// get signature reference + fn signature(&self) -> &Signature { + &self.signature + } + + /// Verifies the signature of a Block + fn verify_signature(&self) -> bool { + self.block.using_encoded(|p| { + self.signature.verify(p, &self.block.block_data().block_author.into()) + }) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::traits::{Block as BlockT, BlockData, Header, SignBlock}; + use sp_core::Pair; + use std::time::{SystemTime, UNIX_EPOCH}; + + /// gets the timestamp of the block as seconds since unix epoch + fn timestamp_now() -> Timestamp { + SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_millis() as Timestamp + } + + fn test_block() -> Block { + let header = Header::new(0, H256::random(), H256::random(), Default::default(), 1); + let block_data = BlockData::new( + ed25519::Pair::from_string("//Alice", None).unwrap().public().into(), + H256::random(), + Default::default(), + Default::default(), + timestamp_now(), + ); + + Block::new(header, block_data) + } + + #[test] + fn signing_works() { + let block = test_block(); + let signer = ed25519::Pair::from_string("//Alice", None).unwrap(); + + let signature: Signature = + Signature::Ed25519(signer.sign(block.encode().as_slice().into())); + let signed_block: SignedBlock = block.clone().sign_block(&signer); + + assert_eq!(signed_block.block(), &block); + assert_eq!(signed_block.signature(), &signature); + assert!(signed_block.verify_signature()); + } + + #[test] + fn tampered_block_verify_signature_fails() { + let signer = ed25519::Pair::from_string("//Alice", None).unwrap(); + + let mut signed_block: SignedBlock = test_block().sign_block(&signer); + signed_block.block.header.block_number = 1; + + assert!(!signed_block.verify_signature()); + } +} diff --git a/tee-worker/sidechain/primitives/src/types/block_data.rs b/tee-worker/sidechain/primitives/src/types/block_data.rs new file mode 100644 index 0000000000..a48d4148e4 --- /dev/null +++ b/tee-worker/sidechain/primitives/src/types/block_data.rs @@ -0,0 +1,82 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +use crate::traits::BlockData as BlockDataTrait; +use codec::{Decode, Encode}; +use sp_core::{ed25519, H256}; +use sp_std::vec::Vec; + +pub type Timestamp = u64; + +#[cfg(feature = "std")] +use serde::{Deserialize, Serialize}; + +#[derive(PartialEq, Eq, Clone, Encode, Decode, Debug)] +#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] +pub struct BlockData { + pub timestamp: u64, + /// Parentchain header this block is based on. + pub layer_one_head: H256, + /// Must be registered on layer one as an enclave for the respective shard. + pub block_author: ed25519::Public, + /// Hashes of signed trusted operations. + pub signed_top_hashes: Vec, + /// Encrypted state payload. + pub encrypted_state_diff: Vec, +} + +impl BlockDataTrait for BlockData { + type Public = ed25519::Public; + + /// Get timestamp of block. + fn timestamp(&self) -> Timestamp { + self.timestamp + } + /// Get layer one head of block. + fn layer_one_head(&self) -> H256 { + self.layer_one_head + } + /// Get author of block. + fn block_author(&self) -> &Self::Public { + &self.block_author + } + /// Get reference of extrinisics of block. + fn signed_top_hashes(&self) -> &[H256] { + &self.signed_top_hashes + } + /// Get encrypted payload. + fn encrypted_state_diff(&self) -> &Vec { + &self.encrypted_state_diff + } + /// Constructs block data. + fn new( + block_author: Self::Public, + layer_one_head: H256, + signed_top_hashes: Vec, + encrypted_state_diff: Vec, + timestamp: Timestamp, + ) -> BlockData { + // create block + BlockData { + timestamp, + layer_one_head, + signed_top_hashes, + block_author, + encrypted_state_diff, + } + } +} diff --git a/tee-worker/sidechain/primitives/src/types/header.rs b/tee-worker/sidechain/primitives/src/types/header.rs new file mode 100644 index 0000000000..a4289193f8 --- /dev/null +++ b/tee-worker/sidechain/primitives/src/types/header.rs @@ -0,0 +1,91 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +//!Primitives for the sidechain +use crate::traits::Header as HeaderTrait; +use codec::{Decode, Encode}; +use scale_info::TypeInfo; +use sp_core::H256; +use sp_runtime::traits::{BlakeTwo256, Hash}; +use sp_std::prelude::*; + +#[cfg(feature = "std")] +use serde::{Deserialize, Serialize}; + +pub type ShardIdentifier = H256; + +#[derive(PartialEq, Eq, Clone, Encode, Decode, Debug, Copy, Default, TypeInfo)] +#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] +pub struct SidechainHeader { + /// The parent hash. + pub parent_hash: H256, + + /// The block number. + pub block_number: u64, + + /// The Shard id. + pub shard_id: ShardIdentifier, + + /// The payload hash. + pub block_data_hash: H256, + + /// The latest finalized block number + pub next_finalization_block_number: u64, +} + +impl SidechainHeader { + /// get the `blake2_256` hash of the header. + pub fn hash(&self) -> H256 { + self.using_encoded(BlakeTwo256::hash) + } +} + +impl HeaderTrait for SidechainHeader { + type ShardIdentifier = H256; + + fn block_number(&self) -> u64 { + self.block_number + } + fn parent_hash(&self) -> H256 { + self.parent_hash + } + fn shard_id(&self) -> Self::ShardIdentifier { + self.shard_id + } + fn block_data_hash(&self) -> H256 { + self.block_data_hash + } + fn next_finalization_block_number(&self) -> u64 { + self.next_finalization_block_number + } + + fn new( + block_number: u64, + parent_hash: H256, + shard: Self::ShardIdentifier, + block_data_hash: H256, + next_finalization_block_number: u64, + ) -> SidechainHeader { + SidechainHeader { + block_number, + parent_hash, + shard_id: shard, + block_data_hash, + next_finalization_block_number, + } + } +} diff --git a/tee-worker/sidechain/primitives/src/types/mod.rs b/tee-worker/sidechain/primitives/src/types/mod.rs new file mode 100644 index 0000000000..2056953387 --- /dev/null +++ b/tee-worker/sidechain/primitives/src/types/mod.rs @@ -0,0 +1,22 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +pub mod block; +pub mod block_data; +pub mod header; + +pub use block::*; diff --git a/tee-worker/sidechain/rpc-handler/Cargo.toml b/tee-worker/sidechain/rpc-handler/Cargo.toml new file mode 100644 index 0000000000..689024ce51 --- /dev/null +++ b/tee-worker/sidechain/rpc-handler/Cargo.toml @@ -0,0 +1,51 @@ +[package] +authors = ["Integritee AG "] +edition = "2021" +name = "its-rpc-handler" +version = "0.9.0" + +[dependencies] +# sgx dependencies +sgx_tstd = { branch = "master", git = "https://github.com/apache/teaclave-sgx-sdk.git", optional = true } +sgx_types = { branch = "master", git = "https://github.com/apache/teaclave-sgx-sdk.git" } + +# local dependencies +itp-rpc = { path = "../../core-primitives/rpc", default-features = false } +itp-top-pool-author = { path = "../../core-primitives/top-pool-author", default-features = false } +itp-types = { path = "../../core-primitives/types", default-features = false } +itp-utils = { path = "../../core-primitives/utils", default-features = false } +its-primitives = { path = "../primitives", default-features = false } + +# sgx enabled external libraries +jsonrpc-core_sgx = { package = "jsonrpc-core", git = "https://github.com/scs/jsonrpc", branch = "no_std_v18", default-features = false, optional = true } +rust-base58_sgx = { package = "rust-base58", rev = "sgx_1.1.3", git = "https://github.com/mesalock-linux/rust-base58-sgx", optional = true, default-features = false, features = ["mesalock_sgx"] } + +# std compatible external libraries (make sure these versions match with the sgx-enabled ones above) +jsonrpc-core = { version = "18", optional = true } +rust-base58 = { package = "rust-base58", version = "0.0.4", optional = true } + +# no-std compatible libraries +codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } +log = { version = "0.4", default-features = false } +sp-core = { default-features = false, features = ["full_crypto"], git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.29" } + +[features] +default = ["std"] +sgx = [ + "sgx_tstd", + "itp-rpc/sgx", + "itp-top-pool-author/sgx", + "itp-utils/sgx", + "jsonrpc-core_sgx", + "rust-base58_sgx", +] +std = [ + "itp-rpc/std", + "itp-top-pool-author/std", + "itp-types/std", + "itp-utils/std", + "its-primitives/std", + "jsonrpc-core", + "log/std", + "rust-base58", +] diff --git a/tee-worker/sidechain/rpc-handler/src/constants.rs b/tee-worker/sidechain/rpc-handler/src/constants.rs new file mode 100644 index 0000000000..b3b5659b82 --- /dev/null +++ b/tee-worker/sidechain/rpc-handler/src/constants.rs @@ -0,0 +1,22 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +//! Sidechain constants + +// RPC method names. +pub const RPC_METHOD_NAME_IMPORT_BLOCKS: &str = "sidechain_importBlock"; +pub const RPC_METHOD_NAME_FETCH_BLOCKS_FROM_PEER: &str = "sidechain_fetchBlocksFromPeer"; diff --git a/tee-worker/sidechain/rpc-handler/src/direct_top_pool_api.rs b/tee-worker/sidechain/rpc-handler/src/direct_top_pool_api.rs new file mode 100644 index 0000000000..69086fbecc --- /dev/null +++ b/tee-worker/sidechain/rpc-handler/src/direct_top_pool_api.rs @@ -0,0 +1,155 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +#[cfg(all(not(feature = "std"), feature = "sgx"))] +use crate::sgx_reexport_prelude::*; + +#[cfg(feature = "std")] +use rust_base58::base58::FromBase58; + +#[cfg(feature = "sgx")] +use base58::FromBase58; + +use codec::{Decode, Encode}; +use itp_rpc::RpcReturnValue; +use itp_top_pool_author::traits::AuthorApi; +use itp_types::{DirectRequestStatus, Request, ShardIdentifier, TrustedOperationStatus}; +use itp_utils::{FromHexPrefixed, ToHexPrefixed}; +use jsonrpc_core::{ + futures::executor, serde_json::json, Error as RpcError, IoHandler, Params, Value, +}; +use log::*; +use std::{borrow::ToOwned, format, string::String, sync::Arc, vec, vec::Vec}; + +type Hash = sp_core::H256; + +pub fn add_top_pool_direct_rpc_methods( + top_pool_author: Arc, + mut io_handler: IoHandler, +) -> IoHandler +where + R: AuthorApi + Send + Sync + 'static, +{ + // author_submitAndWatchExtrinsic + let author_submit_and_watch_extrinsic_name: &str = "author_submitAndWatchExtrinsic"; + let watch_author = top_pool_author.clone(); + io_handler.add_sync_method(author_submit_and_watch_extrinsic_name, move |params: Params| { + let json_value = match author_submit_extrinsic_inner(watch_author.clone(), params) { + Ok(hash_value) => RpcReturnValue { + do_watch: true, + value: hash_value.encode(), + status: DirectRequestStatus::TrustedOperationStatus( + TrustedOperationStatus::Submitted, + ), + } + .to_hex(), + Err(error) => compute_hex_encoded_return_error(error.as_str()), + }; + Ok(json!(json_value)) + }); + + // author_submitExtrinsic + let author_submit_extrinsic_name: &str = "author_submitExtrinsic"; + let submit_author = top_pool_author.clone(); + io_handler.add_sync_method(author_submit_extrinsic_name, move |params: Params| { + let json_value = match author_submit_extrinsic_inner(submit_author.clone(), params) { + Ok(hash_value) => RpcReturnValue { + do_watch: false, + value: hash_value.encode(), + status: DirectRequestStatus::TrustedOperationStatus( + TrustedOperationStatus::Submitted, + ), + } + .to_hex(), + Err(error) => compute_hex_encoded_return_error(error.as_str()), + }; + Ok(json!(json_value)) + }); + + // author_pendingExtrinsics + let author_pending_extrinsic_name: &str = "author_pendingExtrinsics"; + let pending_author = top_pool_author; + io_handler.add_sync_method(author_pending_extrinsic_name, move |params: Params| { + match params.parse::>() { + Ok(shards) => { + let mut retrieved_operations = vec![]; + for shard_base58 in shards.iter() { + let shard = match decode_shard_from_base58(shard_base58.as_str()) { + Ok(id) => id, + Err(msg) => return Ok(Value::String(msg)), + }; + if let Ok(vec_of_operations) = pending_author.pending_tops(shard) { + retrieved_operations.push(vec_of_operations); + } + } + let json_value = RpcReturnValue { + do_watch: false, + value: retrieved_operations.encode(), + status: DirectRequestStatus::Ok, + }; + Ok(json!(json_value.to_hex())) + }, + Err(e) => { + let error_msg: String = format!("Could not retrieve pending calls due to: {}", e); + Ok(json!(compute_hex_encoded_return_error(error_msg.as_str()))) + }, + } + }); + + io_handler +} + +// converts the rpc methods vector to a string and adds commas and brackets for readability +fn decode_shard_from_base58(shard_base58: &str) -> Result { + let shard_vec = match shard_base58.from_base58() { + Ok(vec) => vec, + Err(_) => return Err("Invalid base58 format of shard id".to_owned()), + }; + let shard = match ShardIdentifier::decode(&mut shard_vec.as_slice()) { + Ok(hash) => hash, + Err(_) => return Err("Shard ID is not of type H256".to_owned()), + }; + Ok(shard) +} + +fn compute_hex_encoded_return_error(error_msg: &str) -> String { + RpcReturnValue::from_error_message(error_msg).to_hex() +} + +fn author_submit_extrinsic_inner + Send + Sync + 'static>( + author: Arc, + params: Params, +) -> Result { + debug!("Author submit and watch trusted operation.."); + + let hex_encoded_params = params.parse::>().map_err(|e| format!("{:?}", e))?; + + let request = + Request::from_hex(&hex_encoded_params[0].clone()).map_err(|e| format!("{:?}", e))?; + + let shard: ShardIdentifier = request.shard; + let encrypted_trusted_call: Vec = request.cyphertext; + let result = async { author.watch_top(encrypted_trusted_call, shard).await }; + let response: Result = executor::block_on(result); + + match &response { + Ok(h) => debug!("Trusted operation submitted successfully ({:?})", h), + Err(e) => warn!("Submitting trusted operation failed: {:?}", e), + } + + response.map_err(|e| format!("{:?}", e)) +} diff --git a/tee-worker/sidechain/rpc-handler/src/import_block_api.rs b/tee-worker/sidechain/rpc-handler/src/import_block_api.rs new file mode 100644 index 0000000000..a34ff829ef --- /dev/null +++ b/tee-worker/sidechain/rpc-handler/src/import_block_api.rs @@ -0,0 +1,126 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +#[cfg(all(not(feature = "std"), feature = "sgx"))] +use crate::sgx_reexport_prelude::*; + +use crate::constants::RPC_METHOD_NAME_IMPORT_BLOCKS; +use itp_utils::FromHexPrefixed; +use its_primitives::types::SignedBlock; +use jsonrpc_core::{IoHandler, Params, Value}; +use log::*; +use std::{borrow::ToOwned, fmt::Debug, string::String, vec::Vec}; + +pub fn add_import_block_rpc_method( + import_fn: ImportFn, + mut io_handler: IoHandler, +) -> IoHandler +where + ImportFn: Fn(SignedBlock) -> Result<(), Error> + Sync + Send + 'static, + Error: Debug, +{ + let sidechain_import_import_name: &str = RPC_METHOD_NAME_IMPORT_BLOCKS; + io_handler.add_sync_method(sidechain_import_import_name, move |sidechain_blocks: Params| { + debug!("{} rpc. Params: {:?}", RPC_METHOD_NAME_IMPORT_BLOCKS, sidechain_blocks); + + let hex_encoded_block_vec: Vec = sidechain_blocks.parse()?; + + let blocks = Vec::::from_hex(&hex_encoded_block_vec[0]).map_err(|_| { + jsonrpc_core::error::Error::invalid_params_with_details( + "Could not decode Vec", + hex_encoded_block_vec, + ) + })?; + + debug!("{}. Blocks: {:?}", RPC_METHOD_NAME_IMPORT_BLOCKS, blocks); + + for block in blocks { + info!("Add block {} to import queue", block.block.header.block_number); + let _ = import_fn(block).map_err(|e| { + let error = jsonrpc_core::error::Error::invalid_params_with_details( + "Failed to import Block.", + e, + ); + error!("{:?}", error); + }); + } + + Ok(Value::String("ok".to_owned())) + }); + + io_handler +} + +#[cfg(test)] +pub mod tests { + + use super::*; + + fn rpc_response(result: T) -> String { + format!(r#"{{"jsonrpc":"2.0","result":{},"id":1}}"#, result.to_string()) + } + + fn io_handler() -> IoHandler { + let io_handler = IoHandler::new(); + add_import_block_rpc_method::<_, String>(|_| Ok(()), io_handler) + } + + #[test] + pub fn sidechain_import_block_is_ok() { + let io = io_handler(); + let enclave_req = r#"{"jsonrpc":"2.0","method":"sidechain_importBlock","params":["0x04a7417cf9370af5ea5cf64f107aa49ebf320dbf10c6d0ef200ef7c5d57c9f4b956d000000000000007dba6b8e1f8f38f7f517dbd4a3eaeb27a97958d7a1d1541f69db5d24b3c48cd0dc376b08fcb44dca19a08a0445023a5f4bef80019b518296313e83fc105c669064000000000000005f08a5f98301000081bd02d7e1f8b6ab9a64fa8fdaa379fc1c9208bf0d341689c2342ce8a314e174768f40dfe0fadf2e7347f2ec83a541427a0931ce54ce7a4506184198c2e7aed3006d031b2cc662bbcd54ca1cc09f0021d956673c4905b07edf0b9f323d2078fc4d8cbaefe34353bc731f9a1ef14dfd6b58274a6efbbc6c2c4261d304b979305f501819df33452f2f276add2f3650b825c700abf23790a6787baf1cabb208633eb33fb66e987a99193fbd2c07374502dc0fdff6d7a5d462b2a9c0196711437aa6a30ce52ae6e4818a643df256c026b08d7ccca2de46f368630512073b271397719f34c9b8612c7f1707d06b45206da268f49b5b5159b3418093512700ecb67ccbc5bd9a1731a9c67372b39ec3761d12afb445a6c8580b97a090f4bb06ff70001bc44f7f91ada7f92f0064188d08c16594ddb4fd09f65bee5f4b3c92b80091d3fe5bc89f3fb95a96941563126a6379b806981dd7f225c7e3ac4e1ee0509de406"],"id":1}"#; + + let response_string = io.handle_request_sync(enclave_req).unwrap(); + + assert_eq!(response_string, rpc_response("\"ok\"")); + } + + #[test] + pub fn sidechain_import_block_returns_invalid_param_err() { + let io = io_handler(); + let enclave_req = + r#"{"jsonrpc":"2.0","method":"sidechain_importBlock","params":[4,214,133,100],"id":1}"#; + + let response_string = io.handle_request_sync(enclave_req).unwrap(); + + let err_msg = r#"{"jsonrpc":"2.0","error":{"code":-32602,"message":"Invalid params: invalid type: integer `4`, expected a string."},"id":1}"#; + assert_eq!(response_string, err_msg); + } + + #[test] + pub fn sidechain_import_block_returns_decode_err() { + let io = io_handler(); + let enclave_req = r#"{"jsonrpc":"2.0","method":"sidechain_importBlock","params":["SophisticatedInvalidParam"],"id":1}"#; + + let response_string = io.handle_request_sync(enclave_req).unwrap(); + + let err_msg = r#"{"jsonrpc":"2.0","error":{"code":-32602,"message":"Invalid parameters: Could not decode Vec","data":"[\"SophisticatedInvalidParam\"]"},"id":1}"#; + assert_eq!(response_string, err_msg); + } + + pub fn sidechain_import_block_returns_decode_err_for_valid_hex() { + let io = io_handler(); + + let enclave_req = + r#"{"jsonrpc":"2.0","method":"sidechain_importBlock","params": ["0x11"],"id":1}"#; + + let response_string = io.handle_request_sync(enclave_req).unwrap(); + + let err_msg = r#"{"jsonrpc":"2.0","error":{"code":-32602,"message":"Invalid parameters: Could not decode Vec","data":"[17]"},"id":1}"#; + assert_eq!(response_string, err_msg); + } +} diff --git a/tee-worker/sidechain/rpc-handler/src/lib.rs b/tee-worker/sidechain/rpc-handler/src/lib.rs new file mode 100644 index 0000000000..7d51909986 --- /dev/null +++ b/tee-worker/sidechain/rpc-handler/src/lib.rs @@ -0,0 +1,36 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +#![feature(trait_alias)] +#![cfg_attr(not(feature = "std"), no_std)] + +#[cfg(all(feature = "std", feature = "sgx"))] +compile_error!("feature \"std\" and feature \"sgx\" cannot be enabled at the same time"); + +#[cfg(all(not(feature = "std"), feature = "sgx"))] +extern crate sgx_tstd as std; + +// re-export module to properly feature gate sgx and regular std environment +#[cfg(all(not(feature = "std"), feature = "sgx"))] +pub mod sgx_reexport_prelude { + pub use jsonrpc_core_sgx as jsonrpc_core; + pub use rust_base58_sgx as base58; +} + +pub mod constants; +pub mod direct_top_pool_api; +pub mod import_block_api; diff --git a/tee-worker/sidechain/sidechain-crate/Cargo.toml b/tee-worker/sidechain/sidechain-crate/Cargo.toml new file mode 100644 index 0000000000..0c3c0bd1c9 --- /dev/null +++ b/tee-worker/sidechain/sidechain-crate/Cargo.toml @@ -0,0 +1,36 @@ +[package] +authors = ["Integritee AG "] +edition = "2021" +name = "its-sidechain" +version = "0.9.0" + +[features] +default = ["std"] +sgx = [ + "its-block-composer/sgx", + "its-consensus-aura/sgx", + "its-consensus-common/sgx", + "its-consensus-slots/sgx", + "its-rpc-handler/sgx", + "its-state/sgx", +] +std = [ + "its-block-composer/std", + "its-consensus-aura/std", + "its-consensus-common/std", + "its-consensus-slots/std", + "its-rpc-handler/std", + "its-primitives/std", + "its-state/std", + "its-validateer-fetch/std", +] + +[dependencies] +its-block-composer = { path = "../block-composer", default-features = false } +its-consensus-aura = { path = "../consensus/aura", default-features = false } +its-consensus-common = { path = "../consensus/common", default-features = false } +its-consensus-slots = { path = "../consensus/slots", default-features = false } +its-primitives = { path = "../primitives", default-features = false } +its-rpc-handler = { path = "../rpc-handler", default-features = false } +its-state = { path = "../state", default-features = false } +its-validateer-fetch = { path = "../validateer-fetch", default-features = false } diff --git a/tee-worker/sidechain/sidechain-crate/src/lib.rs b/tee-worker/sidechain/sidechain-crate/src/lib.rs new file mode 100644 index 0000000000..59821318a8 --- /dev/null +++ b/tee-worker/sidechain/sidechain-crate/src/lib.rs @@ -0,0 +1,39 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +//! Reexport all the sidechain stuff in one crate + +#![cfg_attr(not(feature = "std"), no_std)] + +#[cfg(all(feature = "std", feature = "sgx"))] +compile_error!("feature \"std\" and feature \"sgx\" cannot be enabled at the same time"); + +pub use its_block_composer as block_composer; + +pub use its_consensus_aura as aura; + +pub use its_consensus_common as consensus_common; + +pub use its_consensus_slots as slots; + +pub use its_primitives as primitives; + +pub use its_rpc_handler as rpc_handler; + +pub use its_state as state; + +pub use its_validateer_fetch as validateer_fetch; diff --git a/tee-worker/sidechain/state/Cargo.toml b/tee-worker/sidechain/state/Cargo.toml new file mode 100644 index 0000000000..8bd5c51716 --- /dev/null +++ b/tee-worker/sidechain/state/Cargo.toml @@ -0,0 +1,62 @@ +[package] +authors = ["Integritee AG "] +edition = "2021" +name = "its-state" +version = "0.9.0" + +[dependencies] +codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive", "chain-error"] } +frame-support = { default-features = false, git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.29" } +log = { version = "0.4", default-features = false } +serde = { version = "1.0", default-features = false, features = ["alloc", "derive"] } + +# optional std deps +thiserror = { version = "1.0.9", optional = true } + +# sgx deps +sgx_tstd = { branch = "master", git = "https://github.com/apache/teaclave-sgx-sdk.git", optional = true } + +# sgx forks +thiserror_sgx = { package = "thiserror", version = "1.0.9", git = "https://github.com/mesalock-linux/thiserror-sgx", tag = "sgx_1.1.3", optional = true } + +# local deps +itp-sgx-externalities = { default-features = false, path = "../../core-primitives/substrate-sgx/externalities" } +itp-storage = { path = "../../core-primitives/storage", default-features = false } +its-primitives = { path = "../primitives", default-features = false } +sp-io = { optional = true, default-features = false, features = ["disable_oom", "disable_panic_handler", "disable_allocator"], path = "../../core-primitives/substrate-sgx/sp-io" } + +# substrate deps +sp-core = { default-features = false, git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.29" } +sp-std = { default-features = false, git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.29" } + +# test deps +[dev-dependencies] +sp-runtime = { default-features = false, git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.29" } + +[features] +default = ["std"] +sgx = [ + # teaclave + "sgx_tstd", + # local crates + "itp-sgx-externalities/sgx", + "itp-storage/sgx", + "sp-io/sgx", + # sgx versions of std crates + "thiserror_sgx", +] +std = [ + "log/std", + "serde/std", + # substrate + "sp-std/std", + "sp-core/std", + # local crates + "itp-sgx-externalities/std", + "itp-storage/std", + "its-primitives/std", + "sp-io/std", + # optional std crates + "codec/std", + "thiserror", +] diff --git a/tee-worker/sidechain/state/src/error.rs b/tee-worker/sidechain/state/src/error.rs new file mode 100644 index 0000000000..82838b376e --- /dev/null +++ b/tee-worker/sidechain/state/src/error.rs @@ -0,0 +1,31 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +#[cfg(all(not(feature = "std"), feature = "sgx"))] +use crate::sgx_reexports::*; + +use std::string::String; + +#[derive(Debug, thiserror::Error, PartialEq, Eq)] +pub enum Error { + #[error("Invalid apriori state hash supplied")] + InvalidAprioriHash, + #[error("Invalid storage diff")] + InvalidStorageDiff, + #[error("Codec error when accessing module: {1}, storage: {2}. Error: {0:?}")] + DB(codec::Error, String, String), +} diff --git a/tee-worker/sidechain/state/src/impls.rs b/tee-worker/sidechain/state/src/impls.rs new file mode 100644 index 0000000000..1311a934a0 --- /dev/null +++ b/tee-worker/sidechain/state/src/impls.rs @@ -0,0 +1,265 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +//! Implement the sidechain state traits. + +use crate::{Error, SidechainDB, SidechainState, StateUpdate}; +use codec::{Decode, Encode}; +use frame_support::ensure; +use itp_sgx_externalities::{SgxExternalitiesTrait, StateHash}; +use itp_storage::keys::storage_value_key; +use log::{error, info}; +use sp_core::H256; +use sp_io::{storage, KillStorageResult}; +use std::vec::Vec; + +impl SidechainState for SidechainDB +where + T: SgxExternalitiesTrait + StateHash + Clone, + ::SgxExternalitiesType: Encode, + SidechainBlock: Clone, +{ + type Externalities = T; + type StateUpdate = StateUpdate; + type Hash = H256; + + fn state_hash(&self) -> Self::Hash { + self.ext.hash() + } + + fn ext(&self) -> &Self::Externalities { + &self.ext + } + + fn ext_mut(&mut self) -> &mut Self::Externalities { + &mut self.ext + } + + fn apply_state_update(&mut self, state_payload: &Self::StateUpdate) -> Result<(), Error> { + self.ext_mut().apply_state_update(state_payload) + } + + fn get_with_name(&self, module_prefix: &str, storage_prefix: &str) -> Option { + self.ext().get_with_name(module_prefix, storage_prefix) + } + + fn set_with_name(&mut self, module_prefix: &str, storage_prefix: &str, value: V) { + self.ext_mut().set_with_name(module_prefix, storage_prefix, value) + } + + fn clear_with_name(&mut self, module_prefix: &str, storage_prefix: &str) { + self.ext_mut().clear_with_name(module_prefix, storage_prefix) + } + + fn clear_prefix_with_name( + &mut self, + module_prefix: &str, + storage_prefix: &str, + ) -> KillStorageResult { + self.ext_mut().clear_prefix_with_name(module_prefix, storage_prefix) + } + + fn get(&self, key: &[u8]) -> Option> { + self.ext().get(key).cloned() + } + + fn set(&mut self, key: &[u8], value: &[u8]) { + self.ext_mut().set(key, value) + } + + fn clear(&mut self, key: &[u8]) { + self.ext_mut().clear(key) + } + + fn clear_sidechain_prefix(&mut self, prefix: &[u8]) -> KillStorageResult { + self.ext_mut().clear_sidechain_prefix(prefix) + } +} + +impl SidechainState for T +where + ::SgxExternalitiesType: Encode, +{ + type Externalities = Self; + type StateUpdate = StateUpdate; + type Hash = H256; + + fn state_hash(&self) -> Self::Hash { + self.hash() + } + + fn ext(&self) -> &Self::Externalities { + self + } + + fn ext_mut(&mut self) -> &mut Self::Externalities { + self + } + + fn apply_state_update(&mut self, state_payload: &Self::StateUpdate) -> Result<(), Error> { + info!("Current state size: {}", self.ext().state().encoded_size()); + ensure!(self.state_hash() == state_payload.state_hash_apriori(), Error::InvalidAprioriHash); + let mut state2 = self.clone(); + + state2.execute_with(|| { + state_payload.state_update.iter().for_each(|(k, v)| { + match v { + Some(value) => storage::set(k, value), + None => storage::clear(k), + }; + }) + }); + + ensure!(state2.hash() == state_payload.state_hash_aposteriori(), Error::InvalidStorageDiff); + *self = state2; + self.prune_state_diff(); + Ok(()) + } + + fn get_with_name(&self, module_prefix: &str, storage_prefix: &str) -> Option { + let res = self + .get(&storage_value_key(module_prefix, storage_prefix)) + .map(|v| Decode::decode(&mut v.as_slice())) + .transpose(); + + match res { + Ok(res) => res, + Err(e) => { + error!( + "Error decoding storage: {}, {}. Error: {:?}", + module_prefix, storage_prefix, e + ); + None + }, + } + } + + fn set_with_name(&mut self, module_prefix: &str, storage_prefix: &str, value: V) { + self.set(&storage_value_key(module_prefix, storage_prefix), &value.encode()) + } + + fn clear_with_name(&mut self, module_prefix: &str, storage_prefix: &str) { + self.clear(&storage_value_key(module_prefix, storage_prefix)) + } + + fn clear_prefix_with_name( + &mut self, + module_prefix: &str, + storage_prefix: &str, + ) -> KillStorageResult { + self.clear_sidechain_prefix(&storage_value_key(module_prefix, storage_prefix)) + } + + fn get(&self, key: &[u8]) -> Option> { + self.get(key).cloned() + } + + fn set(&mut self, key: &[u8], value: &[u8]) { + self.execute_with(|| sp_io::storage::set(key, value)) + } + + fn clear(&mut self, key: &[u8]) { + self.execute_with(|| sp_io::storage::clear(key)) + } + + fn clear_sidechain_prefix(&mut self, prefix: &[u8]) -> KillStorageResult { + self.execute_with(|| sp_io::storage::clear_prefix(prefix, None)) + } +} + +#[cfg(test)] +pub mod tests { + use super::*; + use crate::{SidechainDB, StateUpdate}; + use frame_support::{assert_err, assert_ok}; + use itp_sgx_externalities::{SgxExternalities, SgxExternalitiesTrait}; + use sp_core::H256; + + pub fn default_db() -> SidechainDB<(), SgxExternalities> { + SidechainDB::<(), SgxExternalities>::default() + } + + #[test] + pub fn apply_state_update_works() { + let mut state1 = default_db(); + let mut state2 = default_db(); + + let apriori = state1.state_hash(); + state1.set(b"Hello", b"World"); + let aposteriori = state1.state_hash(); + + let mut state_update = + StateUpdate::new(apriori, aposteriori, state1.ext.state_diff.clone()); + + assert_ok!(state2.apply_state_update(&mut state_update)); + assert_eq!(state2.state_hash(), aposteriori); + assert_eq!(state2.get(b"Hello").unwrap(), b"World"); + assert!(state2.ext.state_diff.is_empty()); + } + + #[test] + pub fn apply_state_update_returns_storage_hash_mismatch_err() { + let mut state1 = default_db(); + let mut state2 = default_db(); + + let apriori = H256::from([1; 32]); + state1.set(b"Hello", b"World"); + let aposteriori = state1.state_hash(); + + let mut state_update = + StateUpdate::new(apriori, aposteriori, state1.ext.state_diff.clone()); + + assert_err!(state2.apply_state_update(&mut state_update), Error::InvalidAprioriHash); + assert_eq!(state2, default_db()); + } + + #[test] + pub fn apply_state_update_returns_invalid_storage_diff_err() { + let mut state1 = default_db(); + let mut state2 = default_db(); + + let apriori = state1.state_hash(); + state1.set(b"Hello", b"World"); + let aposteriori = H256::from([1; 32]); + + let mut state_update = + StateUpdate::new(apriori, aposteriori, state1.ext.state_diff.clone()); + + assert_err!(state2.apply_state_update(&mut state_update), Error::InvalidStorageDiff); + assert_eq!(state2, default_db()); + } + + #[test] + pub fn sp_io_storage_set_creates_storage_diff() { + let mut state1 = default_db(); + + state1.ext.execute_with(|| { + storage::set(b"hello", b"world"); + }); + + assert_eq!(state1.ext.state_diff.get(&b"hello"[..]).unwrap(), &Some(b"world".encode())); + } + + #[test] + pub fn create_state_diff_without_setting_externalities_works() { + let mut state1 = default_db(); + + state1.set(b"hello", b"world"); + + assert_eq!(state1.ext.state_diff.get(&b"hello"[..]).unwrap(), &Some(b"world".encode())); + } +} diff --git a/tee-worker/sidechain/state/src/lib.rs b/tee-worker/sidechain/state/src/lib.rs new file mode 100644 index 0000000000..95c78523ff --- /dev/null +++ b/tee-worker/sidechain/state/src/lib.rs @@ -0,0 +1,227 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +#![cfg_attr(not(feature = "std"), no_std)] + +#[cfg(all(feature = "std", feature = "sgx"))] +compile_error!("feature \"std\" and feature \"sgx\" cannot be enabled at the same time"); + +#[cfg(all(not(feature = "std"), feature = "sgx"))] +extern crate sgx_tstd as std; + +mod error; +mod impls; + +pub use error::*; +pub use impls::*; + +#[cfg(all(not(feature = "std"), feature = "sgx"))] +mod sgx_reexports { + pub use thiserror_sgx as thiserror; +} + +use codec::{Decode, Encode}; +use itp_sgx_externalities::{SgxExternalitiesDiffType, SgxExternalitiesTrait, StateHash}; +use its_primitives::{ + traits::Block as SidechainBlockTrait, + types::{BlockHash, BlockNumber, Timestamp}, +}; +use sp_core::H256; +use sp_io::KillStorageResult; +use sp_std::prelude::Vec; +use std::marker::PhantomData; + +/// Sidechain wrapper and interface of the STF state. +/// +/// TODO: In the course of refactoring the STF (#269), verify if this struct is even needed. +/// It might be that we could implement everything directly on `[SgxExternalities]`. +#[derive(Clone, Debug, Default, Encode, Decode, PartialEq, Eq)] +pub struct SidechainDB { + /// Externalities + pub ext: E, + _phantom: PhantomData, +} + +impl SidechainDB { + pub fn new(externalities: E) -> Self { + Self { ext: externalities, _phantom: Default::default() } + } +} + +/// Contains the necessary data to update the `SidechainDB` when importing a `SidechainBlock`. +#[derive(PartialEq, Eq, Clone, Debug, Encode, Decode)] +pub struct StateUpdate { + /// state hash before the `state_update` was applied. + state_hash_apriori: H256, + /// state hash after the `state_update` was applied. + state_hash_aposteriori: H256, + /// state diff applied to state with hash `state_hash_apriori` + /// leading to state with hash `state_hash_aposteriori` + state_update: SgxExternalitiesDiffType, +} + +impl StateUpdate { + /// get state hash before the `state_update` was applied. + pub fn state_hash_apriori(&self) -> H256 { + self.state_hash_apriori + } + /// get state hash after the `state_update` was applied. + pub fn state_hash_aposteriori(&self) -> H256 { + self.state_hash_aposteriori + } + /// reference to the `state_update` + pub fn state_update(&self) -> &SgxExternalitiesDiffType { + &self.state_update + } + + /// create new `StatePayload` instance. + pub fn new(apriori: H256, aposteriori: H256, update: SgxExternalitiesDiffType) -> StateUpdate { + StateUpdate { + state_hash_apriori: apriori, + state_hash_aposteriori: aposteriori, + state_update: update, + } + } +} +/// Abstraction around the sidechain state. +pub trait SidechainState: Clone { + type Externalities: SgxExternalitiesTrait + StateHash; + + type StateUpdate: Encode + Decode; + + type Hash; + + /// get the hash of the state + fn state_hash(&self) -> Self::Hash; + + /// get a reference to the underlying externalities of the state + fn ext(&self) -> &Self::Externalities; + + /// get a mutable reference to the underlying externalities of the state + fn ext_mut(&mut self) -> &mut Self::Externalities; + + /// apply the state update to the state + fn apply_state_update(&mut self, state_payload: &Self::StateUpdate) -> Result<(), Error>; + + /// get a storage value by its full name + fn get_with_name(&self, module_prefix: &str, storage_prefix: &str) -> Option; + + /// set a storage value by its full name + fn set_with_name(&mut self, module_prefix: &str, storage_prefix: &str, value: V); + + /// Clear a storage value by its full name + fn clear_with_name(&mut self, module_prefix: &str, storage_prefix: &str); + + /// Clear all storage values for the given prefix. + fn clear_prefix_with_name( + &mut self, + module_prefix: &str, + storage_prefix: &str, + ) -> KillStorageResult; + + /// get a storage value by its storage hash + fn get(&self, key: &[u8]) -> Option>; + + /// set a storage value by its storage hash + fn set(&mut self, key: &[u8], value: &[u8]); + + /// Clear a storage value by its storage hash. + fn clear(&mut self, key: &[u8]); + + /// Clear a all storage values starting the given prefix. + fn clear_sidechain_prefix(&mut self, prefix: &[u8]) -> KillStorageResult; +} + +/// trait to set and get the last sidechain block of the sidechain state +pub trait LastBlockExt { + /// get the last block of the sidechain state + fn get_last_block(&self) -> Option; + + /// set the last block of the sidechain state + fn set_last_block(&mut self, block: &SidechainBlock); +} + +impl LastBlockExt + for SidechainDB +where + SidechainDB: SidechainState + SidechainSystemExt, +{ + fn get_last_block(&self) -> Option { + self.get_with_name("System", "LastBlock") + } + + fn set_last_block(&mut self, block: &SidechainBlock) { + self.set_last_block_hash(&block.hash()); + self.set_with_name("System", "LastBlock", block) + } +} + +/// System extension for the `SidechainDB`. +pub trait SidechainSystemExt { + /// Get the last block number. + fn get_block_number(&self) -> Option; + + /// Set the last block number. + fn set_block_number(&mut self, number: &BlockNumber); + + /// Get the last block hash. + fn get_last_block_hash(&self) -> Option; + + /// Set the last block hash. + fn set_last_block_hash(&mut self, hash: &BlockHash); + + /// Get the timestamp of. + fn get_timestamp(&self) -> Option; + + /// Set the timestamp. + fn set_timestamp(&mut self, timestamp: &Timestamp); + + /// Resets the events. + fn reset_events(&mut self); +} + +impl SidechainSystemExt for T { + fn get_block_number(&self) -> Option { + self.get_with_name("System", "Number") + } + + fn set_block_number(&mut self, number: &BlockNumber) { + self.set_with_name("System", "Number", number) + } + + fn get_last_block_hash(&self) -> Option { + self.get_with_name("System", "LastHash") + } + + fn set_last_block_hash(&mut self, hash: &BlockHash) { + self.set_with_name("System", "LastHash", hash) + } + + fn get_timestamp(&self) -> Option { + self.get_with_name("System", "Timestamp") + } + + fn set_timestamp(&mut self, timestamp: &Timestamp) { + self.set_with_name("System", "Timestamp", timestamp) + } + + fn reset_events(&mut self) { + self.clear_with_name("System", "Events"); + self.clear_with_name("System", "EventCount"); + self.clear_prefix_with_name("System", "EventTopics"); + } +} diff --git a/tee-worker/sidechain/storage/Cargo.toml b/tee-worker/sidechain/storage/Cargo.toml new file mode 100644 index 0000000000..f7a7b60794 --- /dev/null +++ b/tee-worker/sidechain/storage/Cargo.toml @@ -0,0 +1,31 @@ +[package] +authors = ["Integritee AG "] +edition = "2021" +name = "its-storage" +version = "0.9.0" + +[dependencies] +# crate.io +codec = { package = "parity-scale-codec", version = "3.0.0", features = ["derive"] } +log = "0.4" +parking_lot = "0.12.1" +rocksdb = { version = "0.18.0", default_features = false } +thiserror = "1.0" + +# integritee +itp-types = { path = "../../core-primitives/types" } +its-primitives = { path = "../primitives" } + +# Substrate dependencies +sp-core = { git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.29" } + +[dev-dependencies] +# crate.io +mockall = "0.11" +temp-dir = "0.1" +# local +itp-time-utils = { path = "../../core-primitives/time-utils" } +its-test = { path = "../test" } + +[features] +mocks = [] diff --git a/tee-worker/sidechain/storage/src/db.rs b/tee-worker/sidechain/storage/src/db.rs new file mode 100644 index 0000000000..6e51a749fb --- /dev/null +++ b/tee-worker/sidechain/storage/src/db.rs @@ -0,0 +1,67 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +use super::{Error, Result}; +use codec::{Decode, Encode}; +use rocksdb::{WriteBatch, DB}; +use std::path::PathBuf; + +/// Sidechain DB Storage structure: +/// STORED_SHARDS_KEY -> Vec<(Shard)> +/// (LAST_BLOCK_KEY, Shard) -> (Blockhash, BlockNr) (look up current blockchain state) +/// (Shard , Block number) -> Blockhash (needed for block pruning) +/// Blockhash -> Signed Block (actual block storage) + +/// Interface struct to rocks DB +pub struct SidechainDB { + db: DB, +} + +impl SidechainDB { + pub fn open_default(path: PathBuf) -> Result { + Ok(SidechainDB { db: DB::open_default(path)? }) + } + + /// returns the decoded value of the DB entry, if there is one + pub fn get(&self, key: K) -> Result> { + match self.db.get(key.encode())? { + None => Ok(None), + Some(encoded_hash) => Ok(Some(V::decode(&mut encoded_hash.as_slice())?)), + } + } + + /// writes a batch to the DB + pub fn write(&mut self, batch: WriteBatch) -> Result<()> { + self.db.write(batch).map_err(Error::Operational) + } + + /// adds a given key value pair to the batch + pub fn add_to_batch(batch: &mut WriteBatch, key: K, value: V) { + batch.put(key.encode(), &value.encode()) + } + + /// adds a delte key command to the batch + pub fn delete_to_batch(batch: &mut WriteBatch, key: K) { + batch.delete(key.encode()) + } + + /// add an entry to the DB + #[cfg(test)] + pub fn put(&mut self, key: K, value: V) -> Result<()> { + self.db.put(key.encode(), value.encode()).map_err(Error::Operational) + } +} diff --git a/tee-worker/sidechain/storage/src/error.rs b/tee-worker/sidechain/storage/src/error.rs new file mode 100644 index 0000000000..983909f1a4 --- /dev/null +++ b/tee-worker/sidechain/storage/src/error.rs @@ -0,0 +1,34 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +use thiserror::Error; + +pub type Result = std::result::Result; + +#[derive(Error, Debug)] +pub enum Error { + #[error("Could not interact with file storage: {0:?}")] + Operational(#[from] rocksdb::Error), + #[error("Last Block of shard {0} not found")] + LastBlockNotFound(String), + #[error("Failed to find parent block")] + FailedToFindParentBlock, + #[error("Could not decode: {0:?}")] + Decode(#[from] codec::Error), + #[error("Given block is not a successor of the last known block")] + HeaderAncestryMismatch, +} diff --git a/tee-worker/sidechain/storage/src/fetch_blocks_mock.rs b/tee-worker/sidechain/storage/src/fetch_blocks_mock.rs new file mode 100644 index 0000000000..17b77fd22f --- /dev/null +++ b/tee-worker/sidechain/storage/src/fetch_blocks_mock.rs @@ -0,0 +1,53 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +use crate::{error::Result, interface::FetchBlocks}; +use its_primitives::{ + traits::ShardIdentifierFor, + types::{BlockHash, SignedBlock}, +}; + +#[derive(Default)] +pub struct FetchBlocksMock { + blocks_to_be_fetched: Vec, +} + +impl FetchBlocksMock { + pub fn with_blocks(mut self, blocks: Vec) -> Self { + self.blocks_to_be_fetched = blocks; + self + } +} + +impl FetchBlocks for FetchBlocksMock { + fn fetch_all_blocks_after( + &self, + _block_hash: &BlockHash, + _shard_identifier: &ShardIdentifierFor, + ) -> Result> { + Ok(self.blocks_to_be_fetched.clone()) + } + + fn fetch_blocks_in_range( + &self, + _block_hash_from: &BlockHash, + _block_hash_until: &BlockHash, + _shard_identifier: &ShardIdentifierFor, + ) -> Result> { + Ok(self.blocks_to_be_fetched.clone()) + } +} diff --git a/tee-worker/sidechain/storage/src/interface.rs b/tee-worker/sidechain/storage/src/interface.rs new file mode 100644 index 0000000000..4954107754 --- /dev/null +++ b/tee-worker/sidechain/storage/src/interface.rs @@ -0,0 +1,113 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +#[cfg(test)] +use mockall::predicate::*; +#[cfg(test)] +use mockall::*; + +use super::{storage::SidechainStorage, Result}; +use its_primitives::{ + traits::{ShardIdentifierFor, SignedBlock as SignedBlockT}, + types::{BlockHash, BlockNumber}, +}; +use parking_lot::RwLock; +use std::path::PathBuf; + +/// Lock wrapper around sidechain storage +pub struct SidechainStorageLock { + storage: RwLock>, +} + +impl SidechainStorageLock { + pub fn new(path: PathBuf) -> Result> { + Ok(SidechainStorageLock { + storage: RwLock::new(SidechainStorage::::new(path)?), + }) + } +} + +/// Storage interface Trait +#[cfg_attr(test, automock)] +pub trait BlockStorage { + // Type is not working because broadcaster needs to work with the same block type, + // so it needs to be defined somewhere more global. + // type SignedBlock: SignedBlockT; + fn store_blocks(&self, blocks: Vec) -> Result<()>; +} + +pub trait BlockPruner { + /// Prune all blocks except the newest n, where n = `number_of_blocks_to_keep`. + fn prune_blocks_except(&self, number_of_blocks_to_keep: u64); +} + +#[cfg_attr(test, automock)] +pub trait FetchBlocks { + /// Fetch all child blocks of a specified block. + /// + /// Returns an empty vector if specified block hash cannot be found in storage. + fn fetch_all_blocks_after( + &self, + block_hash: &BlockHash, + shard_identifier: &ShardIdentifierFor, + ) -> Result>; + + /// Fetch all blocks within a range, defined by a starting block (lower bound) and end block (upper bound) hash. + /// + /// Does NOT include the bound defining blocks in the result. ]from..until[. + /// Returns an empty vector if 'from' cannot be found in storage. + /// Returns the same as 'fetch_all_blocks_after' if 'until' cannot be found in storage. + fn fetch_blocks_in_range( + &self, + block_hash_from: &BlockHash, + block_hash_until: &BlockHash, + shard_identifier: &ShardIdentifierFor, + ) -> Result>; +} + +impl BlockStorage for SidechainStorageLock { + fn store_blocks(&self, blocks: Vec) -> Result<()> { + self.storage.write().store_blocks(blocks) + } +} + +impl BlockPruner for SidechainStorageLock { + fn prune_blocks_except(&self, number_of_blocks_to_keep: BlockNumber) { + self.storage.write().prune_shards(number_of_blocks_to_keep); + } +} + +impl FetchBlocks for SidechainStorageLock { + fn fetch_all_blocks_after( + &self, + block_hash: &BlockHash, + shard_identifier: &ShardIdentifierFor, + ) -> Result> { + self.storage.read().get_blocks_after(block_hash, shard_identifier) + } + + fn fetch_blocks_in_range( + &self, + block_hash_from: &BlockHash, + block_hash_until: &BlockHash, + shard_identifier: &ShardIdentifierFor, + ) -> Result> { + self.storage + .read() + .get_blocks_in_range(block_hash_from, block_hash_until, shard_identifier) + } +} diff --git a/tee-worker/sidechain/storage/src/lib.rs b/tee-worker/sidechain/storage/src/lib.rs new file mode 100644 index 0000000000..e6e68cc2ef --- /dev/null +++ b/tee-worker/sidechain/storage/src/lib.rs @@ -0,0 +1,69 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +#![cfg_attr(test, feature(assert_matches))] + +use its_primitives::types::BlockNumber; +use std::{ + sync::Arc, + thread, + time::{Duration, SystemTime}, +}; + +mod db; +mod error; +pub mod interface; +mod storage; + +#[cfg(test)] +mod storage_tests_get_blocks_after; + +#[cfg(test)] +mod storage_tests_get_blocks_in_range; + +#[cfg(test)] +mod test_utils; + +#[cfg(feature = "mocks")] +pub mod fetch_blocks_mock; + +pub use error::{Error, Result}; +pub use interface::{BlockPruner, BlockStorage, SidechainStorageLock}; + +pub fn start_sidechain_pruning_loop( + storage: &Arc, + purge_interval: u64, + purge_limit: BlockNumber, +) where + D: BlockPruner, +{ + let interval_time = Duration::from_secs(purge_interval); + let mut interval_start = SystemTime::now(); + loop { + if let Ok(elapsed) = interval_start.elapsed() { + if elapsed >= interval_time { + // update interval time + interval_start = SystemTime::now(); + storage.prune_blocks_except(purge_limit); + } else { + // sleep for the rest of the interval + let sleep_time = interval_time - elapsed; + thread::sleep(sleep_time); + } + } + } +} diff --git a/tee-worker/sidechain/storage/src/storage.rs b/tee-worker/sidechain/storage/src/storage.rs new file mode 100644 index 0000000000..74b6db5320 --- /dev/null +++ b/tee-worker/sidechain/storage/src/storage.rs @@ -0,0 +1,1172 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +use super::{db::SidechainDB, Error, Result}; +use codec::{Decode, Encode}; +use its_primitives::{ + traits::{Block as BlockTrait, Header as HeaderTrait, SignedBlock as SignedBlockT}, + types::{BlockHash, BlockNumber}, +}; +use log::*; +use rocksdb::WriteBatch; +use sp_core::H256; +use std::{collections::HashMap, fmt::Debug, path::PathBuf}; +/// key value of sidechain db of last block +const LAST_BLOCK_KEY: &[u8] = b"last_sidechainblock"; +/// key value of the stored shards vector +const STORED_SHARDS_KEY: &[u8] = b"stored_shards"; + +/// ShardIdentifier type +type ShardIdentifierFor = + <<::Block as BlockTrait>::HeaderType as HeaderTrait>::ShardIdentifier; + +/// Helper struct, contains the blocknumber +/// and blockhash of the last sidechain block +#[derive(PartialEq, Eq, Clone, Copy, Encode, Decode, Debug, Default)] +pub struct LastSidechainBlock { + /// hash of the last sidechain block + pub hash: H256, + /// block number of the last sidechain block + pub number: BlockNumber, +} + +/// Struct used to insert newly produced sidechainblocks +/// into the database +pub struct SidechainStorage { + /// database + db: SidechainDB, + /// shards in database + shards: Vec>, + /// map to last sidechain block of every shard + last_blocks: HashMap, LastSidechainBlock>, +} + +impl SidechainStorage { + /// loads the DB from the given paths and stores the listed shard + /// and their last blocks in memory for better performance + pub fn new(path: PathBuf) -> Result> { + // load db + let db = SidechainDB::open_default(path)?; + let mut storage = SidechainStorage { db, shards: vec![], last_blocks: HashMap::new() }; + storage.shards = storage.load_shards_from_db()?; + // get last block of each shard + for shard in storage.shards.iter() { + if let Some(last_block) = storage.load_last_block_from_db(shard)? { + storage.last_blocks.insert(*shard, last_block); + } else { + // an empty shard sidechain storage should not exist. Consider deleting this shard from the shards list. + error!("Sidechain storage of shard {:?} is empty", shard); + } + } + Ok(storage) + } + + /// gets all shards of currently loaded sidechain db + pub fn shards(&self) -> &Vec> { + &self.shards + } + + /// gets the last block of the current sidechain DB and the given shard + pub fn last_block_of_shard( + &self, + shard: &ShardIdentifierFor, + ) -> Option<&LastSidechainBlock> { + self.last_blocks.get(shard) + } + + /// gets the block hash of the sidechain block of the given shard and block number, if there is such a block + pub fn get_block_hash( + &self, + shard: &ShardIdentifierFor, + block_number: BlockNumber, + ) -> Result> { + self.db.get((*shard, block_number)) + } + + /// gets the block of the given blockhash, if there is such a block + #[allow(unused)] + pub fn get_block(&self, block_hash: &BlockHash) -> Result> { + self.db.get(block_hash) + } + + /// Get all blocks after (i.e. children of) a specified block. + pub fn get_blocks_after( + &self, + block_hash: &BlockHash, + shard_identifier: &ShardIdentifierFor, + ) -> Result> { + // Ensure we find the block in storage (otherwise we would return all blocks for a specific shard). + // The exception is, if the hash is the default hash, which represents block 0. In that case we want to return all blocks. + if block_hash != &BlockHash::default() && self.get_block(block_hash)?.is_none() { + warn!("Could not find starting block in storage, returning empty vector"); + return Ok(Vec::new()) + } + + // We get the latest block and then traverse the parents until we find our starting block. + let last_block_of_shard = self.last_block_of_shard(shard_identifier).ok_or_else(|| { + Error::LastBlockNotFound("Failed to find last block information".to_string()) + })?; + let latest_block = self.get_block(&last_block_of_shard.hash)?.ok_or_else(|| { + Error::LastBlockNotFound("Failed to retrieve last block from storage".to_string()) + })?; + + let mut current_block = latest_block; + let mut blocks_to_return = Vec::::new(); + while ¤t_block.hash() != block_hash { + let parent_block_hash = current_block.block().header().parent_hash(); + + blocks_to_return.push(current_block); + + if parent_block_hash == BlockHash::default() { + break + } + + current_block = + self.get_block(&parent_block_hash)?.ok_or(Error::FailedToFindParentBlock)?; + } + + // Reverse because we iterate from newest to oldest, but result should be oldest first. + blocks_to_return.reverse(); + + Ok(blocks_to_return) + } + + /// Get blocks in a range, defined by 'from' and 'until' (result does NOT include the bound defining blocks). + pub fn get_blocks_in_range( + &self, + block_hash_from: &BlockHash, + block_hash_until: &BlockHash, + shard_identifier: &ShardIdentifierFor, + ) -> Result> { + let all_blocks_from_lower_bound = + self.get_blocks_after(block_hash_from, shard_identifier)?; + + Ok(all_blocks_from_lower_bound + .into_iter() + .take_while(|b| b.hash() != *block_hash_until) + .collect()) + } + + /// Update sidechain storage with blocks. + /// + /// Blocks are iterated through one by one. In case more than one block per shard is included, + /// be sure to give them in the correct order (oldest first). + pub fn store_blocks(&mut self, blocks_to_store: Vec) -> Result<()> { + let mut batch = WriteBatch::default(); + let mut new_shard = false; + for block in blocks_to_store.into_iter() { + if let Err(e) = self.add_block_to_batch(&block, &mut new_shard, &mut batch) { + error!("Could not store block {:?} due to: {:?}", block, e); + }; + } + // Update stored_shards_key -> vec only if a new shard was included, + if new_shard { + SidechainDB::add_to_batch(&mut batch, STORED_SHARDS_KEY, self.shards().clone()); + } + // Store everything. + self.db.write(batch) + } + + /// purges a shard and its block from the db storage + pub fn purge_shard(&mut self, shard: &ShardIdentifierFor) -> Result<()> { + // get last block of shard + let last_block = self.get_last_block_of_shard(shard)?; + + // remove last block from db storage + let mut batch = WriteBatch::default(); + self.delete_last_block(&mut batch, &last_block, shard); + + // Remove the rest of the blocks from the db + let mut current_block_number = last_block.number; + while let Some(previous_block) = self.get_previous_block(shard, current_block_number)? { + current_block_number = previous_block.number; + self.delete_block(&mut batch, &previous_block.hash, ¤t_block_number, shard); + } + // Remove shard from list. + // STORED_SHARDS_KEY -> Vec<(Shard)> + self.shards.retain(|&x| x != *shard); + // Add updated shards to batch. + SidechainDB::add_to_batch(&mut batch, STORED_SHARDS_KEY, &self.shards); + // Update DB + self.db.write(batch) + } + + /// purges a shard and its block from the db storage + /// FIXME: Add delete functions? + pub fn prune_shard_from_block_number( + &mut self, + shard: &ShardIdentifierFor, + block_number: BlockNumber, + ) -> Result<()> { + let last_block = self.get_last_block_of_shard(shard)?; + if last_block.number == block_number { + // given block number is last block of chain - purge whole shard + self.purge_shard(shard) + } else { + // iterate through chain and add all blocks to WriteBatch (delete cmd) + let mut batch = WriteBatch::default(); + let mut current_block_number = block_number; + // Remove blocks from db until no block anymore + while let Some(block_hash) = self.get_block_hash(shard, current_block_number)? { + self.delete_block(&mut batch, &block_hash, ¤t_block_number, shard); + current_block_number -= 1; + } + // Update DB + self.db.write(batch) + } + } + + /// Prunes all shards except for the newest blocks (according to blocknumber). + pub fn prune_shards(&mut self, number_of_blocks_to_keep: BlockNumber) { + for shard in self.shards().clone() { + // get last block: + if let Some(last_block) = self.last_block_of_shard(&shard) { + let threshold_block = last_block.number - number_of_blocks_to_keep; + if let Err(e) = self.prune_shard_from_block_number(&shard, threshold_block) { + error!("Could not purge shard {:?} due to {:?}", shard, e); + } + } else { + error!("Last block not found in shard {:?}", shard); + } + } + } + + fn add_block_to_batch( + &mut self, + signed_block: &SignedBlock, + new_shard: &mut bool, + batch: &mut WriteBatch, + ) -> Result<()> { + let shard = &signed_block.block().header().shard_id(); + if self.shards.contains(shard) { + if !self.verify_block_ancestry(signed_block.block()) { + // Do not include block if its not a direct ancestor of the last block in line. + return Err(Error::HeaderAncestryMismatch) + } + } else { + self.shards.push(*shard); + *new_shard = true; + } + // Add block to DB batch. + self.add_last_block(batch, signed_block); + Ok(()) + } + + fn verify_block_ancestry(&self, block: &::Block) -> bool { + let shard = &block.header().shard_id(); + let current_block_nr = block.header().block_number(); + if let Some(last_block) = self.last_block_of_shard(shard) { + if last_block.number != current_block_nr - 1 { + error!("[Sidechain DB] Sidechainblock (nr: {:?}) is not a succession of the previous block (nr: {:?}) in shard: {:?}", + current_block_nr, last_block.number, *shard); + return false + } + } else { + error!( + "[Sidechain DB] Shard {:?} does not have a last block. Skipping block (nr: {:?}) inclusion", + *shard, current_block_nr + ); + return false + } + true + } + + /// Implementations of helper functions, not meant for pub use + /// gets the previous block of given shard and block number, if there is one. + fn get_previous_block( + &self, + shard: &ShardIdentifierFor, + current_block_number: BlockNumber, + ) -> Result> { + let prev_block_number = current_block_number - 1; + Ok(self + .get_block_hash(shard, prev_block_number)? + .map(|block_hash| LastSidechainBlock { hash: block_hash, number: prev_block_number })) + } + fn load_shards_from_db(&self) -> Result>> { + Ok(self.db.get(STORED_SHARDS_KEY)?.unwrap_or_default()) + } + + fn load_last_block_from_db( + &self, + shard: &ShardIdentifierFor, + ) -> Result> { + self.db.get((LAST_BLOCK_KEY, *shard)) + } + + fn get_last_block_of_shard( + &self, + shard: &ShardIdentifierFor, + ) -> Result { + match self.last_blocks.get(shard) { + Some(last_block) => Ok(*last_block), + None => { + // Try to read from db: + self.load_last_block_from_db(shard)? + .ok_or_else(|| Error::LastBlockNotFound(format!("{:?}", *shard))) + }, + } + } + + /// Adds the block to the WriteBatch. + fn add_last_block(&mut self, batch: &mut WriteBatch, block: &SignedBlock) { + let hash = block.hash(); + let block_number = block.block().header().block_number(); + let shard = block.block().header().shard_id(); + // Block hash -> Signed Block. + SidechainDB::add_to_batch(batch, hash, block); + + // (Shard, Block number) -> Blockhash (for block pruning). + SidechainDB::add_to_batch(batch, (shard, block_number), hash); + + // (last_block_key, shard) -> (Blockhash, BlockNr) current blockchain state. + let last_block = LastSidechainBlock { hash, number: block_number }; + self.last_blocks.insert(shard, last_block); // add in memory + SidechainDB::add_to_batch(batch, (LAST_BLOCK_KEY, shard), last_block); + } + + /// Add delete block to the WriteBatch. + fn delete_block( + &self, + batch: &mut WriteBatch, + block_hash: &H256, + block_number: &BlockNumber, + shard: &ShardIdentifierFor, + ) { + // Block hash -> Signed Block. + SidechainDB::delete_to_batch(batch, block_hash); + // (Shard, Block number) -> Blockhash (for block pruning). + SidechainDB::delete_to_batch(batch, (shard, block_number)); + } + + /// Add delete command to remove last block to WriteBatch and remove it from memory. + /// + /// This includes adding a delete command of the following: + /// - Block hash -> Signed Block. + /// - (Shard, Block number) -> Blockhash (for block pruning). + /// - ((LAST_BLOCK_KEY, shard) -> BlockHash) -> Blockhash (for block pruning). + /// + /// Careful usage of this command: In case the last block is deleted, (LAST_BLOCK_KEY, shard) will be empty + /// even though there might be a new last block (i.e. the previous block of the removed last block). + fn delete_last_block( + &mut self, + batch: &mut WriteBatch, + last_block: &LastSidechainBlock, + shard: &ShardIdentifierFor, + ) { + // Add delete block to batch. + // (LAST_BLOCK_KEY, Shard) -> LastSidechainBlock. + SidechainDB::delete_to_batch(batch, (LAST_BLOCK_KEY, *shard)); + self.delete_block(batch, &last_block.hash, &last_block.number, shard); + + // Delete last block from local memory. + // Careful here: This deletes the local memory before db has been actually pruned + // (it's only been added to the write batch). + // But this can be fixed upon reloading the db / restarting the worker. + self.last_blocks.remove(shard); + } +} + +#[cfg(test)] +mod test { + use super::*; + use crate::test_utils::{ + create_signed_block_with_shard as create_signed_block, create_temp_dir, get_storage, + }; + use itp_types::ShardIdentifier; + use its_primitives::{traits::SignedBlock as SignedBlockT, types::SignedBlock}; + use sp_core::H256; + + #[test] + fn load_shards_from_db_works() { + // given + let temp_dir = create_temp_dir(); + let shard_one = H256::from_low_u64_be(1); + let shard_two = H256::from_low_u64_be(2); + // when + { + let mut sidechain_db = get_storage(temp_dir.path().to_path_buf()); + // ensure db starts empty + assert_eq!(sidechain_db.load_shards_from_db().unwrap(), vec![]); + // write signed_block to db + sidechain_db.db.put(STORED_SHARDS_KEY, vec![shard_one, shard_two]).unwrap(); + } + + // then + { + // open new DB of same path: + let updated_sidechain_db = get_storage(temp_dir.path().to_path_buf()); + let loaded_shards = updated_sidechain_db.load_shards_from_db().unwrap(); + assert!(loaded_shards.contains(&shard_one)); + assert!(loaded_shards.contains(&shard_two)); + } + } + + #[test] + fn load_last_block_from_db_works() { + // given + let temp_dir = create_temp_dir(); + let shard = H256::from_low_u64_be(1); + let signed_block = create_signed_block(20, shard); + let signed_last_block = LastSidechainBlock { + hash: signed_block.hash(), + number: signed_block.block().header().block_number(), + }; + // when + { + let mut sidechain_db = get_storage(temp_dir.path().to_path_buf()); + // ensure db starts empty + assert!(sidechain_db.load_last_block_from_db(&shard).unwrap().is_none()); + // write signed_block to db + sidechain_db.db.put((LAST_BLOCK_KEY, shard), signed_last_block.clone()).unwrap(); + } + + // then + { + // open new DB of same path: + let updated_sidechain_db = get_storage(temp_dir.path().to_path_buf()); + let loaded_block = + updated_sidechain_db.load_last_block_from_db(&shard).unwrap().unwrap(); + assert_eq!(loaded_block, signed_last_block); + } + } + + #[test] + fn create_new_sidechain_storage_works() { + let temp_dir = create_temp_dir(); + let shard = H256::from_low_u64_be(1); + let shard_vector = vec![shard]; + let signed_block = create_signed_block(20, shard); + let signed_last_block = LastSidechainBlock { + hash: signed_block.hash(), + number: signed_block.block().header().block_number(), + }; + { + let mut sidechain_db = get_storage(temp_dir.path().to_path_buf()); + // ensure db starts empty + assert!(sidechain_db.load_last_block_from_db(&shard).unwrap().is_none()); + // write shards to db + sidechain_db.db.put((LAST_BLOCK_KEY, shard), signed_last_block.clone()).unwrap(); + // write shards to db + sidechain_db.db.put(STORED_SHARDS_KEY, shard_vector.clone()).unwrap(); + } + + { + // open new DB of same path: + let updated_sidechain_db = get_storage(temp_dir.path().to_path_buf()); + assert_eq!(updated_sidechain_db.shards, shard_vector); + assert_eq!(*updated_sidechain_db.last_blocks.get(&shard).unwrap(), signed_last_block); + } + } + + #[test] + fn add_last_block_works() { + let temp_dir = create_temp_dir(); + let shard = H256::from_low_u64_be(1); + let signed_block = create_signed_block(8, shard); + + { + let mut sidechain_db = get_storage(temp_dir.path().to_path_buf()); + let mut batch = WriteBatch::default(); + sidechain_db.add_last_block(&mut batch, &signed_block); + sidechain_db.db.write(batch).unwrap(); + + // ensure DB contains previously stored data: + let last_block = sidechain_db.last_block_of_shard(&shard).unwrap(); + assert_eq!(last_block.number, signed_block.block().header().block_number()); + assert_eq!(last_block.hash, signed_block.hash()); + let stored_block_hash = + sidechain_db.get_block_hash(&shard, last_block.number).unwrap().unwrap(); + assert_eq!(stored_block_hash, signed_block.hash()); + assert_eq!(sidechain_db.get_block(&stored_block_hash).unwrap().unwrap(), signed_block); + } + } + + #[test] + fn delete_block_works() { + let temp_dir = create_temp_dir(); + let shard = H256::from_low_u64_be(1); + let signed_block = create_signed_block(8, shard); + { + // fill db + let mut sidechain_db = get_storage(temp_dir.path().to_path_buf()); + sidechain_db.db.put(signed_block.hash(), signed_block.clone()).unwrap(); + sidechain_db + .db + .put((shard, signed_block.block().header().block_number()), signed_block.hash()) + .unwrap(); + assert_eq!( + sidechain_db + .db + .get::<(ShardIdentifier, BlockNumber), H256>(( + shard, + signed_block.block().header().block_number() + )) + .unwrap() + .unwrap(), + signed_block.hash() + ); + assert_eq!( + sidechain_db.db.get::(signed_block.hash()).unwrap().unwrap(), + signed_block + ); + + // when + let mut batch = WriteBatch::default(); + sidechain_db.delete_block( + &mut batch, + &signed_block.hash(), + &signed_block.block().header().block_number(), + &shard, + ); + sidechain_db.db.write(batch).unwrap(); + } + + { + // open new DB of same path: + let updated_sidechain_db = get_storage(temp_dir.path().to_path_buf()); + // ensure DB does not contain block anymore: + assert!(updated_sidechain_db + .db + .get::<(ShardIdentifier, BlockNumber), H256>(( + shard, + signed_block.block().header().block_number() + )) + .unwrap() + .is_none()); + assert!(updated_sidechain_db + .db + .get::(signed_block.hash()) + .unwrap() + .is_none()); + } + } + + #[test] + fn delete_last_block_works() { + let temp_dir = create_temp_dir(); + let shard = H256::from_low_u64_be(1); + let signed_block = create_signed_block(8, shard); + let last_block = LastSidechainBlock { + hash: signed_block.hash(), + number: signed_block.block().header().block_number(), + }; + { + // fill db + let mut sidechain_db = get_storage(temp_dir.path().to_path_buf()); + sidechain_db.db.put(signed_block.hash(), signed_block.clone()).unwrap(); + sidechain_db + .db + .put((shard, signed_block.block().header().block_number()), signed_block.hash()) + .unwrap(); + sidechain_db.db.put((LAST_BLOCK_KEY, shard), last_block.clone()).unwrap(); + assert_eq!( + sidechain_db + .db + .get::<(ShardIdentifier, BlockNumber), H256>(( + shard, + signed_block.block().header().block_number() + )) + .unwrap() + .unwrap(), + signed_block.hash() + ); + assert_eq!( + sidechain_db.db.get::(signed_block.hash()).unwrap().unwrap(), + signed_block + ); + assert_eq!( + sidechain_db + .db + .get::<(&[u8], ShardIdentifier), LastSidechainBlock>((LAST_BLOCK_KEY, shard)) + .unwrap() + .unwrap(), + last_block + ); + + // when + let mut batch = WriteBatch::default(); + sidechain_db.delete_last_block(&mut batch, &last_block, &shard); + sidechain_db.db.write(batch).unwrap(); + + // then + assert!(sidechain_db.last_blocks.get(&shard).is_none()); + assert!(sidechain_db + .db + .get::<(ShardIdentifier, BlockNumber), H256>(( + shard, + signed_block.block().header().block_number() + )) + .unwrap() + .is_none()); + assert!(sidechain_db + .db + .get::(signed_block.hash()) + .unwrap() + .is_none()); + assert!(sidechain_db + .db + .get::<(&[u8], ShardIdentifier), LastSidechainBlock>((LAST_BLOCK_KEY, shard)) + .unwrap() + .is_none()); + } + } + + #[test] + fn verify_block_ancestry_returns_true_if_correct_successor() { + let temp_dir = create_temp_dir(); + let shard = H256::from_low_u64_be(1); + let signed_block = create_signed_block(8, shard); + let last_block = LastSidechainBlock { + hash: signed_block.hash(), + number: signed_block.block().header().block_number(), + }; + let signed_block_two = create_signed_block(9, shard); + { + let mut sidechain_db = get_storage(temp_dir.path().to_path_buf()); + sidechain_db.shards.push(shard); + sidechain_db.last_blocks.insert(shard, last_block); + // when + let result = sidechain_db.verify_block_ancestry(&signed_block_two.block()); + + // then + assert!(result); + } + } + + #[test] + fn verify_block_ancestry_returns_false_if_not_correct_successor() { + let temp_dir = create_temp_dir(); + let shard = H256::from_low_u64_be(1); + let signed_block = create_signed_block(8, shard); + let last_block = LastSidechainBlock { + hash: signed_block.hash(), + number: signed_block.block().header().block_number(), + }; + let signed_block_two = create_signed_block(5, shard); + { + let mut sidechain_db = get_storage(temp_dir.path().to_path_buf()); + sidechain_db.shards.push(shard); + sidechain_db.last_blocks.insert(shard, last_block); + + // when + let result = sidechain_db.verify_block_ancestry(&signed_block_two.block()); + + // then + assert!(!result); + } + } + + #[test] + fn verify_block_ancestry_returns_false_no_last_block_registered() { + let temp_dir = create_temp_dir(); + let shard = H256::from_low_u64_be(1); + let signed_block = create_signed_block(8, shard); + { + let mut sidechain_db = get_storage(temp_dir.path().to_path_buf()); + sidechain_db.shards.push(shard); + // when + let result = sidechain_db.verify_block_ancestry(&signed_block.block()); + + // then + assert!(!result); + } + } + + #[test] + fn verify_block_ancestry_returns_false_if_no_shard() { + let temp_dir = create_temp_dir(); + let shard = H256::from_low_u64_be(1); + let signed_block = create_signed_block(8, shard); + { + let sidechain_db = get_storage(temp_dir.path().to_path_buf()); + let result = sidechain_db.verify_block_ancestry(&signed_block.block()); + assert!(!result); + } + } + + #[test] + fn add_block_to_batch_works_with_new_shard() { + // given + let temp_dir = create_temp_dir(); + let shard = H256::from_low_u64_be(1); + let signed_block = create_signed_block(8, shard); + let mut new_shard = false; + { + let mut sidechain_db = get_storage(temp_dir.path().to_path_buf()); + let mut batch = WriteBatch::default(); + assert!(batch.is_empty()); + + sidechain_db + .add_block_to_batch(&signed_block, &mut new_shard, &mut batch) + .unwrap(); + + assert!(new_shard); + assert!(!batch.is_empty()); + } + } + + #[test] + fn add_block_to_batch_does_not_add_shard_if_existent() { + let temp_dir = create_temp_dir(); + let shard = H256::from_low_u64_be(1); + let signed_block = create_signed_block(8, shard); + let last_block = LastSidechainBlock { + hash: signed_block.hash(), + number: signed_block.block().header().block_number(), + }; + let signed_block_two = create_signed_block(9, shard); + let mut new_shard = false; + { + let mut sidechain_db = get_storage(temp_dir.path().to_path_buf()); + let mut batch = WriteBatch::default(); + assert!(batch.is_empty()); + sidechain_db.shards.push(shard); + sidechain_db.last_blocks.insert(shard, last_block); + + sidechain_db + .add_block_to_batch(&signed_block_two, &mut new_shard, &mut batch) + .unwrap(); + + assert!(!new_shard); + assert!(!batch.is_empty()); + } + } + + #[test] + fn add_block_to_batch_does_not_add_block_if_not_ancestor() { + let temp_dir = create_temp_dir(); + let shard = H256::from_low_u64_be(1); + let signed_block = create_signed_block(8, shard); + let last_block = LastSidechainBlock { + hash: signed_block.hash(), + number: signed_block.block().header().block_number(), + }; + let signed_block_two = create_signed_block(10, shard); + let mut new_shard = false; + { + let mut sidechain_db = get_storage(temp_dir.path().to_path_buf()); + let mut batch = WriteBatch::default(); + sidechain_db.shards.push(shard); + sidechain_db.last_blocks.insert(shard, last_block); + + let result = + sidechain_db.add_block_to_batch(&signed_block_two, &mut new_shard, &mut batch); + + assert!(result.is_err()); + assert!(!new_shard); + assert!(batch.is_empty()); + } + } + + #[test] + fn store_block_works() { + let temp_dir = create_temp_dir(); + let shard = H256::from_low_u64_be(1); + let signed_block = create_signed_block(20, shard); + let signed_block_vector: Vec = vec![signed_block.clone()]; + + { + let mut sidechain_db = get_storage(temp_dir.path().to_path_buf()); + // db needs to start empty + assert_eq!(sidechain_db.shards, vec![]); + sidechain_db.store_blocks(signed_block_vector).unwrap(); + } + + { + // open new DB of same path: + let updated_sidechain_db = get_storage(temp_dir.path().to_path_buf()); + // ensure DB contains previously stored data: + assert_eq!(*updated_sidechain_db.shards(), vec![shard]); + let last_block = updated_sidechain_db.last_block_of_shard(&shard).unwrap(); + assert_eq!(last_block.number, signed_block.block().header().block_number()); + assert_eq!(last_block.hash, signed_block.hash()); + let stored_block_hash = + updated_sidechain_db.get_block_hash(&shard, last_block.number).unwrap().unwrap(); + assert_eq!(stored_block_hash, signed_block.hash()); + assert_eq!( + updated_sidechain_db.get_block(&stored_block_hash).unwrap().unwrap(), + signed_block + ); + } + } + + #[test] + fn store_blocks_on_multi_sharding_works() { + let temp_dir = create_temp_dir(); + let shard_one = H256::from_low_u64_be(1); + let shard_two = H256::from_low_u64_be(2); + let signed_block_one = create_signed_block(20, shard_one); + let signed_block_two = create_signed_block(1, shard_two); + + let signed_block_vector: Vec = + vec![signed_block_one.clone(), signed_block_two.clone()]; + + { + let mut sidechain_db = get_storage(temp_dir.path().to_path_buf()); + // db needs to start empty + assert_eq!(sidechain_db.shards, vec![]); + sidechain_db.store_blocks(signed_block_vector).unwrap(); + } + + { + let updated_sidechain_db = get_storage(temp_dir.path().to_path_buf()); + assert_eq!(updated_sidechain_db.shards()[0], shard_one); + assert_eq!(updated_sidechain_db.shards()[1], shard_two); + let last_block_one: &LastSidechainBlock = + updated_sidechain_db.last_blocks.get(&shard_one).unwrap(); + let last_block_two: &LastSidechainBlock = + updated_sidechain_db.last_blocks.get(&shard_two).unwrap(); + assert_eq!(last_block_one.number, 20); + assert_eq!(last_block_two.number, 1); + assert_eq!(last_block_one.hash, signed_block_one.hash()); + assert_eq!(last_block_two.hash, signed_block_two.hash()); + } + } + + #[test] + fn store_mulitple_block_on_one_shard_works() { + let temp_dir = create_temp_dir(); + let shard = H256::from_low_u64_be(1); + let signed_block_one = create_signed_block(20, shard); + let signed_block_two = create_signed_block(21, shard); + let signed_block_vector_one = vec![signed_block_one.clone()]; + let signed_block_vector_two = vec![signed_block_two.clone()]; + + { + // first iteration + let mut sidechain_db = get_storage(temp_dir.path().to_path_buf()); + sidechain_db.store_blocks(signed_block_vector_one).unwrap(); + } + { + // second iteration + let mut sidechain_db = get_storage(temp_dir.path().to_path_buf()); + sidechain_db.store_blocks(signed_block_vector_two).unwrap(); + } + + { + let updated_sidechain_db = get_storage(temp_dir.path().to_path_buf()); + // last block is really equal to second block: + let last_block: &LastSidechainBlock = + updated_sidechain_db.last_blocks.get(&shard).unwrap(); + assert_eq!(last_block.number, 21); + // storage contains both blocks: + // (shard,blocknumber) -> blockhash + let db_block_hash_one = + updated_sidechain_db.get_block_hash(&shard, 20).unwrap().unwrap(); + let db_block_hash_two = + updated_sidechain_db.get_block_hash(&shard, 21).unwrap().unwrap(); + assert_eq!(db_block_hash_one, signed_block_one.hash()); + assert_eq!(db_block_hash_two, signed_block_two.hash()); + + // block hash -> signed block + let db_block_one = + updated_sidechain_db.get_block(&signed_block_one.hash()).unwrap().unwrap(); + let db_block_two = + updated_sidechain_db.get_block(&signed_block_two.hash()).unwrap().unwrap(); + assert_eq!(db_block_one, signed_block_one); + assert_eq!(db_block_two, signed_block_two); + } + } + + #[test] + fn wrong_succession_order_does_not_get_accepted() { + let temp_dir = create_temp_dir(); + let shard = H256::from_low_u64_be(1); + let signed_block_one = create_signed_block(7, shard); + let signed_block_two = create_signed_block(21, shard); + let signed_block_vector_one = vec![signed_block_one.clone()]; + let signed_block_vector_two = vec![signed_block_two.clone()]; + + { + // first iteration + let mut sidechain_db = get_storage(temp_dir.path().to_path_buf()); + sidechain_db.store_blocks(signed_block_vector_one).unwrap(); + } + { + // second iteration + let mut sidechain_db = get_storage(temp_dir.path().to_path_buf()); + sidechain_db.store_blocks(signed_block_vector_two).unwrap(); + } + { + let updated_sidechain_db = get_storage(temp_dir.path().to_path_buf()); + // last block is equal to first block: + let last_block: &LastSidechainBlock = + updated_sidechain_db.last_blocks.get(&shard).unwrap(); + assert_eq!(last_block.number, signed_block_one.block().header().block_number()); + + // storage contains only one blocks: + // (shard,blocknumber) -> blockhash + let db_block_hash_one = updated_sidechain_db + .get_block_hash(&shard, signed_block_one.block().header().block_number()) + .unwrap() + .unwrap(); + let db_block_hash_empty = updated_sidechain_db + .get_block_hash(&shard, signed_block_two.block().header().block_number()) + .unwrap(); + assert!(db_block_hash_empty.is_none()); + assert_eq!(db_block_hash_one, signed_block_one.hash()); + } + } + + #[test] + fn get_previous_block_returns_correct_block() { + let temp_dir = create_temp_dir(); + let shard = H256::from_low_u64_be(1); + let signed_block_one = create_signed_block(1, shard); + // create sidechain_db + { + let mut sidechain_db = get_storage(temp_dir.path().to_path_buf()); + sidechain_db.store_blocks(vec![signed_block_one.clone()]).unwrap(); + // create last block one for comparison + let last_block = LastSidechainBlock { + hash: signed_block_one.hash(), + number: signed_block_one.block().header().block_number(), + }; + + // then + let some_block = sidechain_db + .get_previous_block(&shard, signed_block_one.block().header().block_number() + 1) + .unwrap() + .unwrap(); + + // when + assert_eq!(some_block, last_block); + } + } + + #[test] + fn get_previous_block_returns_none_when_no_block() { + let temp_dir = create_temp_dir(); + let shard = H256::from_low_u64_be(1); + { + let mut sidechain_db = get_storage(temp_dir.path().to_path_buf()); + sidechain_db.store_blocks(vec![create_signed_block(1, shard)]).unwrap(); + + let no_block = sidechain_db.get_previous_block(&shard, 1).unwrap(); + + assert!(no_block.is_none()); + } + } + + #[test] + fn purge_shard_works() { + let temp_dir = create_temp_dir(); + let shard = H256::from_low_u64_be(1); + let block_one = create_signed_block(1, shard); + let block_two = create_signed_block(2, shard); + let block_three = create_signed_block(3, shard); + { + // create sidechain_db + let mut sidechain_db = get_storage(temp_dir.path().to_path_buf()); + sidechain_db.store_blocks(vec![block_one.clone()]).unwrap(); + sidechain_db.store_blocks(vec![block_two.clone()]).unwrap(); + sidechain_db.store_blocks(vec![block_three.clone()]).unwrap(); + + sidechain_db.purge_shard(&shard).unwrap(); + + // test if local storage has been cleansed + assert!(!sidechain_db.shards.contains(&shard)); + assert!(sidechain_db.last_blocks.get(&shard).is_none()); + } + + { + let updated_sidechain_db = get_storage(temp_dir.path().to_path_buf()); + // test if local storage is still clean + assert!(!updated_sidechain_db.shards.contains(&shard)); + assert!(updated_sidechain_db.last_blocks.get(&shard).is_none()); + // test if db is clean + assert!(updated_sidechain_db.last_block_of_shard(&shard).is_none()); + assert!(updated_sidechain_db.get_block_hash(&shard, 3).unwrap().is_none()); + assert!(updated_sidechain_db.get_block_hash(&shard, 2).unwrap().is_none()); + assert!(updated_sidechain_db.get_block_hash(&shard, 1).unwrap().is_none()); + assert!(updated_sidechain_db.get_block(&block_one.hash()).unwrap().is_none()); + assert!(updated_sidechain_db.get_block(&block_two.hash()).unwrap().is_none()); + assert!(updated_sidechain_db.get_block(&block_three.hash()).unwrap().is_none()); + } + } + + #[test] + fn purge_shard_from_block_works() { + let temp_dir = create_temp_dir(); + let shard = H256::from_low_u64_be(1); + let block_one = create_signed_block(1, shard); + let block_two = create_signed_block(2, shard); + let block_three = create_signed_block(3, shard); + let last_block = LastSidechainBlock { + hash: block_three.hash(), + number: block_three.block().header().block_number(), + }; + + { + // create sidechain_db + let mut sidechain_db = get_storage(temp_dir.path().to_path_buf()); + sidechain_db.store_blocks(vec![block_one.clone()]).unwrap(); + sidechain_db.store_blocks(vec![block_two.clone()]).unwrap(); + sidechain_db.store_blocks(vec![block_three.clone()]).unwrap(); + + sidechain_db.prune_shard_from_block_number(&shard, 2).unwrap(); + } + + { + let updated_sidechain_db = get_storage(temp_dir.path().to_path_buf()); + // test local memory + assert!(updated_sidechain_db.shards.contains(&shard)); + assert_eq!(*updated_sidechain_db.last_blocks.get(&shard).unwrap(), last_block); + // assert block three is still there + assert_eq!(*updated_sidechain_db.last_block_of_shard(&shard).unwrap(), last_block); + assert_eq!( + updated_sidechain_db.get_block_hash(&shard, 3).unwrap().unwrap(), + block_three.hash() + ); + assert_eq!( + updated_sidechain_db.get_block(&block_three.hash()).unwrap().unwrap(), + block_three + ); + // assert the lower blocks have been purged + assert!(updated_sidechain_db.get_block_hash(&shard, 2).unwrap().is_none()); + assert!(updated_sidechain_db.get_block_hash(&shard, 1).unwrap().is_none()); + assert!(updated_sidechain_db.get_block(&block_two.hash()).unwrap().is_none()); + assert!(updated_sidechain_db.get_block(&block_one.hash()).unwrap().is_none()); + } + } + + #[test] + fn purge_shard_from_block_works_for_last_block() { + let temp_dir = create_temp_dir(); + let shard = H256::from_low_u64_be(1); + let block_one = create_signed_block(1, shard); + let block_two = create_signed_block(2, shard); + let block_three = create_signed_block(3, shard); + { + // create sidechain_db + let mut sidechain_db = get_storage(temp_dir.path().to_path_buf()); + sidechain_db.store_blocks(vec![block_one.clone()]).unwrap(); + sidechain_db.store_blocks(vec![block_two.clone()]).unwrap(); + sidechain_db.store_blocks(vec![block_three.clone()]).unwrap(); + + sidechain_db.prune_shard_from_block_number(&shard, 3).unwrap(); + + // test if local storage has been cleansed + assert!(!sidechain_db.shards.contains(&shard)); + assert!(sidechain_db.last_blocks.get(&shard).is_none()); + } + + { + let updated_sidechain_db = get_storage(temp_dir.path().to_path_buf()); + // test if local storage is still clean + assert!(!updated_sidechain_db.shards.contains(&shard)); + assert!(updated_sidechain_db.last_blocks.get(&shard).is_none()); + // test if db is clean + assert!(updated_sidechain_db.last_block_of_shard(&shard).is_none()); + assert!(updated_sidechain_db.get_block_hash(&shard, 3).unwrap().is_none()); + assert!(updated_sidechain_db.get_block_hash(&shard, 2).unwrap().is_none()); + assert!(updated_sidechain_db.get_block_hash(&shard, 1).unwrap().is_none()); + assert!(updated_sidechain_db.get_block(&block_one.hash()).unwrap().is_none()); + assert!(updated_sidechain_db.get_block(&block_two.hash()).unwrap().is_none()); + assert!(updated_sidechain_db.get_block(&block_three.hash()).unwrap().is_none()); + } + } + + #[test] + fn prune_shards_works_for_multiple_shards() { + let temp_dir = create_temp_dir(); + // shard one + let shard_one = H256::from_low_u64_be(1); + let block_one = create_signed_block(1, shard_one); + let block_two = create_signed_block(2, shard_one); + let block_three = create_signed_block(3, shard_one); + let last_block_one = LastSidechainBlock { + hash: block_three.hash(), + number: block_three.block().header().block_number(), + }; + // shard two + let shard_two = H256::from_low_u64_be(2); + let block_one_s = create_signed_block(1, shard_two); + let block_two_s = create_signed_block(2, shard_two); + let block_three_s = create_signed_block(3, shard_two); + let block_four_s = create_signed_block(4, shard_two); + let last_block_two = LastSidechainBlock { + hash: block_four_s.hash(), + number: block_four_s.block().header().block_number(), + }; + { + // create sidechain_db + let mut sidechain_db = get_storage(temp_dir.path().to_path_buf()); + sidechain_db.store_blocks(vec![block_one.clone(), block_one_s.clone()]).unwrap(); + sidechain_db.store_blocks(vec![block_two.clone(), block_two_s.clone()]).unwrap(); + sidechain_db + .store_blocks(vec![block_three.clone(), block_three_s.clone()]) + .unwrap(); + sidechain_db.store_blocks(vec![block_four_s.clone()]).unwrap(); + + sidechain_db.prune_shards(2); + } + + { + let updated_sidechain_db = get_storage(temp_dir.path().to_path_buf()); + // test if shard one has been cleansed of block 1, with 2 and 3 still beeing there: + assert_eq!( + *updated_sidechain_db.last_block_of_shard(&shard_one).unwrap(), + last_block_one + ); + assert_eq!( + updated_sidechain_db.get_block_hash(&shard_one, 3).unwrap().unwrap(), + block_three.hash() + ); + assert_eq!( + updated_sidechain_db.get_block(&block_three.hash()).unwrap().unwrap(), + block_three + ); + assert_eq!( + updated_sidechain_db.get_block_hash(&shard_one, 2).unwrap().unwrap(), + block_two.hash() + ); + assert_eq!( + updated_sidechain_db.get_block(&block_two.hash()).unwrap().unwrap(), + block_two + ); + assert!(updated_sidechain_db.get_block(&block_one.hash()).unwrap().is_none()); + assert!(updated_sidechain_db.get_block_hash(&shard_one, 1).unwrap().is_none()); + // test if shard two has been cleansed of block 1 and 2, with 3 and 4 still beeing there: + assert_eq!( + *updated_sidechain_db.last_block_of_shard(&shard_two).unwrap(), + last_block_two + ); + assert_eq!( + updated_sidechain_db.get_block_hash(&shard_two, 4).unwrap().unwrap(), + block_four_s.hash() + ); + assert_eq!( + updated_sidechain_db.get_block(&block_four_s.hash()).unwrap().unwrap(), + block_four_s + ); + assert_eq!( + updated_sidechain_db.get_block_hash(&shard_two, 3).unwrap().unwrap(), + block_three_s.hash() + ); + assert_eq!( + updated_sidechain_db.get_block(&block_three_s.hash()).unwrap().unwrap(), + block_three_s + ); + assert!(updated_sidechain_db.get_block_hash(&shard_two, 2).unwrap().is_none()); + assert!(updated_sidechain_db.get_block_hash(&shard_two, 1).unwrap().is_none()); + assert!(updated_sidechain_db.get_block(&block_one_s.hash()).unwrap().is_none()); + assert!(updated_sidechain_db.get_block(&block_two_s.hash()).unwrap().is_none()); + } + } +} diff --git a/tee-worker/sidechain/storage/src/storage_tests_get_blocks_after.rs b/tee-worker/sidechain/storage/src/storage_tests_get_blocks_after.rs new file mode 100644 index 0000000000..0795e54ca7 --- /dev/null +++ b/tee-worker/sidechain/storage/src/storage_tests_get_blocks_after.rs @@ -0,0 +1,124 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +use crate::{ + error::Error, + test_utils::{ + create_signed_block_with_parenthash as create_signed_block, default_shard, + fill_storage_with_blocks, get_storage, + }, +}; +use its_primitives::{traits::SignedBlock, types::BlockHash}; +use std::assert_matches::assert_matches; + +#[test] +fn get_blocks_after_works_for_regular_case() { + let block_1 = create_signed_block(1, BlockHash::default()); + let block_2 = create_signed_block(2, block_1.hash()); + let block_3 = create_signed_block(3, block_2.hash()); + let block_4 = create_signed_block(4, block_3.hash()); + + let temp_dir = + fill_storage_with_blocks(vec![block_1.clone(), block_2.clone(), block_3, block_4.clone()]); + + { + let updated_sidechain_db = get_storage(temp_dir.path().to_path_buf()); + let blocks_after_1 = updated_sidechain_db + .get_blocks_after(&block_1.hash(), &default_shard()) + .unwrap(); + + assert_eq!(3, blocks_after_1.len()); + assert_eq!(block_2.hash(), blocks_after_1.first().unwrap().hash()); + assert_eq!(block_4.hash(), blocks_after_1.last().unwrap().hash()); + } +} + +#[test] +fn get_blocks_after_returns_empty_vec_if_block_not_found() { + let block_1 = create_signed_block(1, BlockHash::random()); + + let temp_dir = fill_storage_with_blocks(vec![block_1.clone()]); + + { + let updated_sidechain_db = get_storage(temp_dir.path().to_path_buf()); + let block_hash = BlockHash::from_low_u64_be(1); + // Off-chance that random() generates exactly the same hash + assert_ne!(block_1.hash(), block_hash); + + assert_eq!( + updated_sidechain_db.get_blocks_after(&block_hash, &default_shard()).unwrap(), + Vec::new() + ); + } +} + +#[test] +fn get_blocks_returns_none_if_last_is_already_most_recent_block() { + let block_1 = create_signed_block(1, BlockHash::random()); + + let temp_dir = fill_storage_with_blocks(vec![block_1.clone()]); + + { + let updated_sidechain_db = get_storage(temp_dir.path().to_path_buf()); + + assert_eq!( + updated_sidechain_db + .get_blocks_after(&block_1.hash(), &default_shard()) + .unwrap(), + Vec::new() + ); + } +} + +#[test] +fn get_blocks_after_returns_all_blocks_if_last_known_is_default() { + let block_1 = create_signed_block(1, BlockHash::default()); + let block_2 = create_signed_block(2, block_1.hash()); + let block_3 = create_signed_block(3, block_2.hash()); + + let blocks = vec![block_1.clone(), block_2.clone(), block_3.clone()]; + + let temp_dir = fill_storage_with_blocks(blocks.clone()); + + { + let updated_sidechain_db = get_storage(temp_dir.path().to_path_buf()); + let default_hash = BlockHash::default(); + + assert_eq!( + updated_sidechain_db.get_blocks_after(&default_hash, &default_shard()).unwrap(), + blocks + ); + } +} + +#[test] +fn given_block_with_invalid_ancestry_returns_error() { + let block_1 = create_signed_block(1, BlockHash::default()); + // Should be block_1 hash, but we deliberately introduce an invalid parent hash. + let block_2 = create_signed_block(2, BlockHash::random()); + + let temp_dir = fill_storage_with_blocks(vec![block_1.clone(), block_2]); + + { + let updated_sidechain_db = get_storage(temp_dir.path().to_path_buf()); + + assert_matches!( + updated_sidechain_db.get_blocks_after(&block_1.hash(), &default_shard()), + Err(Error::FailedToFindParentBlock) + ); + } +} diff --git a/tee-worker/sidechain/storage/src/storage_tests_get_blocks_in_range.rs b/tee-worker/sidechain/storage/src/storage_tests_get_blocks_in_range.rs new file mode 100644 index 0000000000..c0c505d4d0 --- /dev/null +++ b/tee-worker/sidechain/storage/src/storage_tests_get_blocks_in_range.rs @@ -0,0 +1,104 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +use crate::test_utils::{ + create_signed_block_with_parenthash as create_signed_block, default_shard, + fill_storage_with_blocks, get_storage, +}; +use itp_types::BlockHash; +use its_primitives::traits::SignedBlock; + +#[test] +fn get_blocks_in_range_works_for_regular_case() { + let block_1 = create_signed_block(1, BlockHash::default()); + let block_2 = create_signed_block(2, block_1.hash()); + let block_3 = create_signed_block(3, block_2.hash()); + let block_4 = create_signed_block(4, block_3.hash()); + let block_5 = create_signed_block(5, block_4.hash()); + + let temp_dir = fill_storage_with_blocks(vec![ + block_1.clone(), + block_2.clone(), + block_3, + block_4.clone(), + block_5.clone(), + ]); + + { + let updated_sidechain_db = get_storage(temp_dir.path().to_path_buf()); + let blocks_2_to_4 = updated_sidechain_db + .get_blocks_in_range(&block_1.hash(), &block_5.hash(), &default_shard()) + .unwrap(); + + assert_eq!(3, blocks_2_to_4.len()); + assert_eq!(block_2.hash(), blocks_2_to_4.first().unwrap().hash()); + assert_eq!(block_4.hash(), blocks_2_to_4.last().unwrap().hash()); + } +} + +#[test] +fn get_blocks_in_range_returns_empty_vec_if_from_is_invalid() { + let block_1 = create_signed_block(1, BlockHash::default()); + let block_2 = create_signed_block(2, block_1.hash()); + let block_3 = create_signed_block(3, block_2.hash()); + let block_4 = create_signed_block(4, block_3.hash()); + + let temp_dir = fill_storage_with_blocks(vec![ + block_1.clone(), + block_2.clone(), + block_3.clone(), + block_4.clone(), + ]); + + { + let updated_sidechain_db = get_storage(temp_dir.path().to_path_buf()); + let invalid_block_hash = BlockHash::from_low_u64_be(1); + + assert!(updated_sidechain_db + .get_blocks_in_range(&invalid_block_hash, &block_3.hash(), &default_shard()) + .unwrap() + .is_empty()); + } +} + +#[test] +fn get_blocks_in_range_returns_all_blocks_if_upper_bound_is_invalid() { + let block_1 = create_signed_block(1, BlockHash::default()); + let block_2 = create_signed_block(2, block_1.hash()); + let block_3 = create_signed_block(3, block_2.hash()); + let block_4 = create_signed_block(4, block_3.hash()); + let block_5 = create_signed_block(5, block_4.hash()); + + let temp_dir = fill_storage_with_blocks(vec![ + block_1.clone(), + block_2.clone(), + block_3.clone(), + block_4.clone(), + block_5.clone(), + ]); + + { + let updated_sidechain_db = get_storage(temp_dir.path().to_path_buf()); + let blocks_in_range = updated_sidechain_db + .get_blocks_in_range(&block_2.hash(), &BlockHash::from_low_u64_be(1), &default_shard()) + .unwrap(); + + assert_eq!(3, blocks_in_range.len()); + assert_eq!(block_3.hash(), blocks_in_range.first().unwrap().hash()); + assert_eq!(block_5.hash(), blocks_in_range.last().unwrap().hash()); + } +} diff --git a/tee-worker/sidechain/storage/src/test_utils.rs b/tee-worker/sidechain/storage/src/test_utils.rs new file mode 100644 index 0000000000..c5c1d694d0 --- /dev/null +++ b/tee-worker/sidechain/storage/src/test_utils.rs @@ -0,0 +1,96 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +use crate::storage::SidechainStorage; +use itp_time_utils::now_as_u64; +use itp_types::ShardIdentifier; +use its_primitives::types::{BlockHash, SignedBlock as SignedSidechainBlock}; +use its_test::{ + sidechain_block_builder::SidechainBlockBuilder, + sidechain_block_data_builder::SidechainBlockDataBuilder, + sidechain_header_builder::SidechainHeaderBuilder, +}; +use sp_core::{crypto::Pair, ed25519, H256}; +use std::{path::PathBuf, vec::Vec}; +use temp_dir::TempDir; + +pub fn fill_storage_with_blocks(blocks: Vec) -> TempDir { + let dir = create_temp_dir(); + let mut sidechain_db = get_storage(dir.path().to_path_buf()); + sidechain_db.store_blocks(blocks).unwrap(); + dir +} + +pub fn create_temp_dir() -> TempDir { + TempDir::new().unwrap() +} + +pub fn get_storage(path: PathBuf) -> SidechainStorage { + SidechainStorage::::new(path).unwrap() +} + +pub fn default_shard() -> ShardIdentifier { + ShardIdentifier::default() +} + +pub fn create_signed_block_with_parenthash( + block_number: u64, + parent_hash: BlockHash, +) -> SignedSidechainBlock { + let header = default_header_builder() + .with_parent_hash(parent_hash) + .with_block_number(block_number) + .build(); + + let block_data = default_block_data_builder().build(); + + SidechainBlockBuilder::default() + .with_header(header) + .with_block_data(block_data) + .build_signed() +} + +pub fn create_signed_block_with_shard( + block_number: u64, + shard: ShardIdentifier, +) -> SignedSidechainBlock { + let header = default_header_builder() + .with_shard(shard) + .with_block_number(block_number) + .build(); + + let block_data = default_block_data_builder().build(); + + SidechainBlockBuilder::default() + .with_header(header) + .with_block_data(block_data) + .build_signed() +} + +fn default_header_builder() -> SidechainHeaderBuilder { + SidechainHeaderBuilder::default() + .with_parent_hash(H256::random()) + .with_block_number(Default::default()) + .with_shard(default_shard()) +} + +fn default_block_data_builder() -> SidechainBlockDataBuilder { + SidechainBlockDataBuilder::default() + .with_timestamp(now_as_u64()) + .with_layer_one_head(H256::random()) + .with_signer(ed25519::Pair::from_string("//Alice", None).unwrap()) +} diff --git a/tee-worker/sidechain/test/Cargo.toml b/tee-worker/sidechain/test/Cargo.toml new file mode 100644 index 0000000000..2d1dc0546d --- /dev/null +++ b/tee-worker/sidechain/test/Cargo.toml @@ -0,0 +1,34 @@ +[package] +authors = ["Integritee AG "] +edition = "2021" +homepage = "https://integritee.network/" +license = "Apache-2.0" +name = "its-test" +repository = "https://github.com/integritee-network/pallets/" +version = "0.9.0" + +[dependencies] +codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false } + +# sgx dependencies +sgx_tstd = { branch = "master", git = "https://github.com/apache/teaclave-sgx-sdk.git", features = ["untrusted_time"], optional = true } + +# Substrate dependencies +sp-core = { default_features = false, git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.29" } + +# local +itp-types = { path = "../../core-primitives/types", default_features = false } +its-primitives = { path = "../primitives", default_features = false, features = ["full_crypto"] } + +[features] +default = ["std"] +sgx = [ + "sgx_tstd", +] +std = [ + "codec/std", + "itp-types/std", + "its-primitives/std", + # substrate + "sp-core/std", +] diff --git a/tee-worker/sidechain/test/src/lib.rs b/tee-worker/sidechain/test/src/lib.rs new file mode 100644 index 0000000000..e9164d6d8b --- /dev/null +++ b/tee-worker/sidechain/test/src/lib.rs @@ -0,0 +1,29 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +#![feature(trait_alias)] +#![cfg_attr(not(feature = "std"), no_std)] + +#[cfg(all(feature = "std", feature = "sgx"))] +compile_error!("feature \"std\" and feature \"sgx\" cannot be enabled at the same time"); + +#[cfg(all(not(feature = "std"), not(feature = "sgx")))] +compile_error!("feature \"std\" and feature \"sgx\" cannot be disabled at the same time"); + +#[cfg(all(not(feature = "std"), feature = "sgx"))] +#[macro_use] +extern crate sgx_tstd as std; + +pub mod sidechain_block_builder; +pub mod sidechain_block_data_builder; +pub mod sidechain_header_builder; diff --git a/tee-worker/sidechain/test/src/sidechain_block_builder.rs b/tee-worker/sidechain/test/src/sidechain_block_builder.rs new file mode 100644 index 0000000000..5db5ab62eb --- /dev/null +++ b/tee-worker/sidechain/test/src/sidechain_block_builder.rs @@ -0,0 +1,90 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + Copyright (C) 2017-2019 Baidu, Inc. All Rights Reserved. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +//! Builder pattern for a signed sidechain block. + +use crate::{ + sidechain_block_data_builder::SidechainBlockDataBuilder, + sidechain_header_builder::SidechainHeaderBuilder, +}; +use its_primitives::{ + traits::SignBlock, + types::{block_data::BlockData, header::SidechainHeader as Header, Block, SignedBlock}, +}; +use sp_core::{ed25519, Pair}; + +type Seed = [u8; 32]; +const ENCLAVE_SEED: Seed = *b"12345678901234567890123456789012"; + +pub struct SidechainBlockBuilder { + signer: ed25519::Pair, + header: Header, + block_data: BlockData, +} + +impl Default for SidechainBlockBuilder { + fn default() -> Self { + SidechainBlockBuilder { + signer: Pair::from_seed(&ENCLAVE_SEED), + header: SidechainHeaderBuilder::default().build(), + block_data: SidechainBlockDataBuilder::default().build(), + } + } +} + +impl SidechainBlockBuilder { + pub fn random() -> Self { + SidechainBlockBuilder { + signer: Pair::from_seed(&ENCLAVE_SEED), + header: SidechainHeaderBuilder::random().build(), + block_data: SidechainBlockDataBuilder::random().build(), + } + } + + pub fn with_header(mut self, header: Header) -> Self { + self.header = header; + self + } + + pub fn with_block_data(mut self, block_data: BlockData) -> Self { + self.block_data = block_data; + self + } + + pub fn with_signer(mut self, signer: ed25519::Pair) -> Self { + self.signer = signer; + self + } + + pub fn build(self) -> Block { + Block { header: self.header, block_data: self.block_data } + } + + pub fn build_signed(self) -> SignedBlock { + let signer = self.signer; + self.build().sign_block(&signer) + } +} + +#[test] +fn build_signed_block_has_valid_signature() { + use its_primitives::traits::SignedBlock as SignedBlockTrait; + + let signed_block = SidechainBlockBuilder::default().build_signed(); + assert!(signed_block.verify_signature()); +} diff --git a/tee-worker/sidechain/test/src/sidechain_block_data_builder.rs b/tee-worker/sidechain/test/src/sidechain_block_data_builder.rs new file mode 100644 index 0000000000..748f96762e --- /dev/null +++ b/tee-worker/sidechain/test/src/sidechain_block_data_builder.rs @@ -0,0 +1,102 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + Copyright (C) 2017-2019 Baidu, Inc. All Rights Reserved. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +//! Builder pattern for sidechain block data. + +use itp_types::H256; +use its_primitives::types::{ + block::{BlockHash, Timestamp}, + block_data::BlockData, +}; +use sp_core::{ed25519, Pair}; +use std::{time::SystemTime, vec}; + +type Seed = [u8; 32]; +const ENCLAVE_SEED: Seed = *b"12345678901234567890123456789012"; + +pub struct SidechainBlockDataBuilder { + timestamp: Timestamp, + layer_one_head: H256, + signer: ed25519::Pair, + signed_top_hashes: Vec, + encrypted_state_diff: Vec, +} + +impl Default for SidechainBlockDataBuilder { + fn default() -> Self { + SidechainBlockDataBuilder { + timestamp: Default::default(), + layer_one_head: Default::default(), + signer: Pair::from_seed(&ENCLAVE_SEED), + signed_top_hashes: Default::default(), + encrypted_state_diff: Default::default(), + } + } +} + +impl SidechainBlockDataBuilder { + pub fn random() -> Self { + SidechainBlockDataBuilder { + timestamp: now_as_u64(), + layer_one_head: BlockHash::random(), + signer: Pair::from_seed(&ENCLAVE_SEED), + signed_top_hashes: vec![H256::random(), H256::random()], + encrypted_state_diff: vec![1, 3, 42, 8, 11, 33], + } + } + + pub fn with_timestamp(mut self, timestamp: Timestamp) -> Self { + self.timestamp = timestamp; + self + } + + pub fn with_signer(mut self, signer: ed25519::Pair) -> Self { + self.signer = signer; + self + } + + pub fn with_layer_one_head(mut self, layer_one_head: H256) -> Self { + self.layer_one_head = layer_one_head; + self + } + + pub fn with_signed_top_hashes(mut self, signed_top_hashes: Vec) -> Self { + self.signed_top_hashes = signed_top_hashes; + self + } + + pub fn with_payload(mut self, payload: Vec) -> Self { + self.encrypted_state_diff = payload; + self + } + + pub fn build(self) -> BlockData { + BlockData { + timestamp: self.timestamp, + block_author: self.signer.public(), + layer_one_head: self.layer_one_head, + signed_top_hashes: self.signed_top_hashes, + encrypted_state_diff: self.encrypted_state_diff, + } + } +} + +/// gets the timestamp of the block as seconds since unix epoch +fn now_as_u64() -> u64 { + SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).unwrap().as_millis() as u64 +} diff --git a/tee-worker/sidechain/test/src/sidechain_header_builder.rs b/tee-worker/sidechain/test/src/sidechain_header_builder.rs new file mode 100644 index 0000000000..fca8b52b8c --- /dev/null +++ b/tee-worker/sidechain/test/src/sidechain_header_builder.rs @@ -0,0 +1,92 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + Copyright (C) 2017-2019 Baidu, Inc. All Rights Reserved. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +//! Builder pattern for a sidechain header. + +use its_primitives::types::{header::SidechainHeader as Header, ShardIdentifier}; +use sp_core::H256; + +pub struct SidechainHeaderBuilder { + parent_hash: H256, + block_number: u64, + shard_id: ShardIdentifier, + block_data_hash: H256, + next_finalization_block_number: u64, +} + +impl Default for SidechainHeaderBuilder { + fn default() -> Self { + SidechainHeaderBuilder { + parent_hash: Default::default(), + block_number: 1, + shard_id: Default::default(), + block_data_hash: Default::default(), + next_finalization_block_number: 1, + } + } +} + +impl SidechainHeaderBuilder { + pub fn random() -> Self { + SidechainHeaderBuilder { + parent_hash: H256::random(), + block_number: 42, + shard_id: ShardIdentifier::random(), + block_data_hash: H256::random(), + next_finalization_block_number: 1, + } + } + + pub fn with_parent_hash(mut self, parent_hash: H256) -> Self { + self.parent_hash = parent_hash; + self + } + + pub fn with_block_number(mut self, block_number: u64) -> Self { + self.block_number = block_number; + self + } + + pub fn with_shard(mut self, shard_id: ShardIdentifier) -> Self { + self.shard_id = shard_id; + self + } + + pub fn with_block_data_hash(mut self, block_data_hash: H256) -> Self { + self.block_data_hash = block_data_hash; + self + } + + pub fn with_next_finalization_block_number( + mut self, + next_finalization_block_number: u64, + ) -> Self { + self.next_finalization_block_number = next_finalization_block_number; + self + } + + pub fn build(self) -> Header { + Header { + parent_hash: self.parent_hash, + block_number: self.block_number, + shard_id: self.shard_id, + block_data_hash: self.block_data_hash, + next_finalization_block_number: self.next_finalization_block_number, + } + } +} diff --git a/tee-worker/sidechain/validateer-fetch/Cargo.toml b/tee-worker/sidechain/validateer-fetch/Cargo.toml new file mode 100644 index 0000000000..3bd3d85b7e --- /dev/null +++ b/tee-worker/sidechain/validateer-fetch/Cargo.toml @@ -0,0 +1,38 @@ +[package] +authors = ["Integritee AG "] +edition = "2021" +name = "its-validateer-fetch" +version = "0.9.0" + +[dependencies] +codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive", "chain-error"] } +derive_more = "0.99.16" +thiserror = "1.0.26" + +# substrate deps +frame-support = { default-features = false, git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.29" } +sp-core = { default-features = false, git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.29" } +sp-runtime = { default-features = false, git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.29" } +sp-std = { default-features = false, git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.29" } + +# local deps +itp-ocall-api = { path = "../../core-primitives/ocall-api", default-features = false } +itp-storage = { path = "../../core-primitives/storage", default-features = false } +itp-teerex-storage = { path = "../../core-primitives/teerex-storage", default-features = false } +itp-types = { path = "../../core-primitives/types", default-features = false } + +[features] +default = ["std"] +std = [ + "codec/std", + "sp-core/std", + "sp-runtime/std", + "sp-std/std", + "itp-types/std", + "itp-storage/std", + "itp-ocall-api/std", +] + +[dev-dependencies] +itc-parentchain-test = { path = "../../core/parentchain/test" } +itp-test = { path = "../../core-primitives/test" } diff --git a/tee-worker/sidechain/validateer-fetch/src/error.rs b/tee-worker/sidechain/validateer-fetch/src/error.rs new file mode 100644 index 0000000000..3b5d4a3f2c --- /dev/null +++ b/tee-worker/sidechain/validateer-fetch/src/error.rs @@ -0,0 +1,27 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +use derive_more::{Display, From}; + +pub type Result = core::result::Result; + +#[derive(Debug, Display, From)] +pub enum Error { + Codec(codec::Error), + Onchain(itp_ocall_api::Error), + Other(&'static str), +} diff --git a/tee-worker/sidechain/validateer-fetch/src/lib.rs b/tee-worker/sidechain/validateer-fetch/src/lib.rs new file mode 100644 index 0000000000..8c68402101 --- /dev/null +++ b/tee-worker/sidechain/validateer-fetch/src/lib.rs @@ -0,0 +1,24 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +#![cfg_attr(not(feature = "std"), no_std)] + +mod error; +mod validateer; + +pub use error::Error; +pub use validateer::*; diff --git a/tee-worker/sidechain/validateer-fetch/src/validateer.rs b/tee-worker/sidechain/validateer-fetch/src/validateer.rs new file mode 100644 index 0000000000..fa79b3e00e --- /dev/null +++ b/tee-worker/sidechain/validateer-fetch/src/validateer.rs @@ -0,0 +1,104 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +use crate::error::{Error, Result}; +use frame_support::ensure; +use itp_ocall_api::EnclaveOnChainOCallApi; +use itp_teerex_storage::{TeeRexStorage, TeerexStorageKeys}; +use itp_types::Enclave; +use sp_core::H256; +use sp_runtime::traits::Header as HeaderT; +use sp_std::prelude::Vec; + +pub trait ValidateerFetch { + fn current_validateers>( + &self, + latest_header: &Header, + ) -> Result>; + fn validateer_count>(&self, latest_header: &Header) + -> Result; +} + +impl ValidateerFetch for OnchainStorage { + fn current_validateers>( + &self, + header: &Header, + ) -> Result> { + let count = self.validateer_count(header)?; + + let mut hashes = Vec::with_capacity(count as usize); + for i in 1..=count { + hashes.push(TeeRexStorage::enclave(i)) + } + + let enclaves: Vec = self + .get_multiple_storages_verified(hashes, header)? + .into_iter() + .filter_map(|e| e.into_tuple().1) + .collect(); + ensure!( + enclaves.len() == count as usize, + Error::Other("Found less validateers onchain than validateer count") + ); + Ok(enclaves) + } + + fn validateer_count>(&self, header: &Header) -> Result { + self.get_storage_verified(TeeRexStorage::enclave_count(), header)? + .into_tuple() + .1 + .ok_or_else(|| Error::Other("Could not get validateer count from chain")) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use codec::Encode; + use itc_parentchain_test::parentchain_header_builder::ParentchainHeaderBuilder; + use itp_test::mock::onchain_mock::{validateer_set, OnchainMock}; + use std::string::ToString; + + #[test] + pub fn get_validateer_count_works() { + let header = ParentchainHeaderBuilder::default().build(); + let mock = OnchainMock::default().add_validateer_set(&header, None); + assert_eq!(mock.validateer_count(&header).unwrap(), 4u64); + } + + #[test] + pub fn get_validateer_set_works() { + let header = ParentchainHeaderBuilder::default().build(); + let mock = OnchainMock::default().add_validateer_set(&header, None); + + let validateers = validateer_set(); + + assert_eq!(mock.current_validateers(&header).unwrap(), validateers); + } + + #[test] + pub fn if_validateer_count_bigger_than_returned_validateers_return_err() { + let header = ParentchainHeaderBuilder::default().build(); + let mut mock = OnchainMock::default().add_validateer_set(&header, None); + mock.insert_at_header(&header, TeeRexStorage::enclave_count(), 5u64.encode()); + + assert_eq!( + mock.current_validateers(&header).unwrap_err().to_string(), + "Found less validateers onchain than validateer count".to_string() + ); + } +} diff --git a/tee-worker/ts-tests/identity.test.ts b/tee-worker/ts-tests/identity.test.ts new file mode 100644 index 0000000000..1e916970b4 --- /dev/null +++ b/tee-worker/ts-tests/identity.test.ts @@ -0,0 +1,73 @@ +import {describeLitentry, generateVerificationMessage,} from './utils' +import {hexToU8a, u8aToHex} from "@polkadot/util"; +import {linkIdentity, setUserShieldingKey, unlinkIdentity, verifyIdentity} from "./indirect_calls"; +import {step} from "mocha-steps"; +import {assert} from "chai"; +import {LitentryIdentity, LitentryValidationData} from "./type-definitions"; + + +const twitterIdentity = { + handle: { + PlainString: `0x${Buffer.from('mock_user', 'utf8').toString("hex")}` + }, + web_type: { + Web2Identity: "Twitter" + } +}; + +const twitterValidationData = { + Web2Validation: { + Twitter: { + tweet_id: `0x${Buffer.from('100', 'utf8').toString('hex')}` + } + } +}; + +const discordIdentity = { + handle: { + PlainString: `0x${Buffer.from('859641379851337798', 'utf8').toString("hex")}` + }, + web_type: { + Web2Identity: "Discord" + } +}; + +const discordValidationData = { + Web2Validation: { + Discord: { + channel_id: `0x${Buffer.from('919848392035794945', 'utf8').toString("hex")}`, + guild_id: `0x${Buffer.from('919848390156767232', 'utf8').toString("hex")}`, + message_id: `0x${Buffer.from('859641379851337798', 'utf8').toString("hex")}`, + } + } +}; + +describeLitentry('Test Identity', (context) => { + const aesKey = '0x22fc82db5b606998ad45099b7978b5b4f9dd4ea6017e57370ac56141caaabd12' + + step('set user shielding key', async function () { + const who = await setUserShieldingKey(context, context.defaultSigner, aesKey, true) + assert.equal(who, u8aToHex(context.defaultSigner.addressRaw), "check caller error") + }) + + step('link twitter identity', async function () { + const r = await linkIdentity(context, context.defaultSigner, aesKey, true, twitterIdentity) + if (r) { + const [_who, challengeCode] = r + console.log("challengeCode: ", challengeCode) + const msg = generateVerificationMessage(context, hexToU8a(challengeCode), context.defaultSigner.addressRaw, twitterIdentity) + console.log("post verification msg to twitter: ", msg) + assert.isNotEmpty(challengeCode, "challengeCode empty") + } + }) + + step('verify twitter identity', async function () { + const who = await verifyIdentity(context, context.defaultSigner, aesKey, true, twitterIdentity, twitterValidationData) + assert.equal(who, u8aToHex(context.defaultSigner.addressRaw), "check caller error") + }) + + step('unlink identity', async function () { + const who = await unlinkIdentity(context, context.defaultSigner, aesKey, true, twitterIdentity) + assert.equal(who, u8aToHex(context.defaultSigner.addressRaw), "check caller error") + }) +}); diff --git a/tee-worker/ts-tests/indirect_calls.ts b/tee-worker/ts-tests/indirect_calls.ts new file mode 100644 index 0000000000..1e06d4d491 --- /dev/null +++ b/tee-worker/ts-tests/indirect_calls.ts @@ -0,0 +1,70 @@ +import {IntegrationTestContext, LitentryIdentity, LitentryValidationData} from "./type-definitions"; +import {encryptWithTeeShieldingKey, listenEncryptedEvents} from "./utils"; +import {KeyringPair} from "@polkadot/keyring/types"; +import {HexString} from "@polkadot/util/types"; + +export async function setUserShieldingKey(context: IntegrationTestContext, signer: KeyringPair, aesKey: HexString, listening: boolean): Promise { + const ciphertext = encryptWithTeeShieldingKey(context.teeShieldingKey, aesKey).toString('hex') + await context.substrate.tx.identityManagement.setUserShieldingKey(context.shard, `0x${ciphertext}`).signAndSend(signer) + if (listening) { + const event = await listenEncryptedEvents(context, aesKey, { + module: "identityManagement", + method: "userShieldingKeySet", + event: "UserShieldingKeySet" + }) + const [who] = event.eventData; + return who + } + return undefined +} + +export async function linkIdentity(context: IntegrationTestContext, signer: KeyringPair, aesKey: HexString, listening: boolean, identity: LitentryIdentity): Promise { + const encode = context.substrate.createType("LitentryIdentity", identity).toHex() + const ciphertext = encryptWithTeeShieldingKey(context.teeShieldingKey, encode).toString('hex') + await context.substrate.tx.identityManagement.linkIdentity(context.shard, `0x${ciphertext}`, null).signAndSend(signer) + if (listening) { + const event = await listenEncryptedEvents(context, aesKey, { + module: "identityManagement", + method: "challengeCodeGenerated", + event: "ChallengeCodeGenerated" + }) + const [who, _identity, challengeCode] = event.eventData; + return [who, challengeCode] + } + return undefined + +} + +export async function unlinkIdentity(context: IntegrationTestContext, signer: KeyringPair, aesKey: HexString, listening: boolean, identity: LitentryIdentity): Promise { + const encode = context.substrate.createType("LitentryIdentity", identity).toHex() + const ciphertext = encryptWithTeeShieldingKey(context.teeShieldingKey, encode).toString('hex') + await context.substrate.tx.identityManagement.unlinkIdentity(context.shard, `0x${ciphertext}`).signAndSend(signer) + if (listening) { + const event = await listenEncryptedEvents(context, aesKey, { + module: "identityManagement", + method: "identityUnlinked", + event: "IdentityUnlinked" + }) + const [who, _identity] = event.eventData; + return who + } + return undefined +} + +export async function verifyIdentity(context: IntegrationTestContext, signer: KeyringPair, aesKey: HexString, listening: boolean, identity: LitentryIdentity, data: LitentryValidationData): Promise { + const identity_encode = context.substrate.createType("LitentryIdentity", identity).toHex() + const validation_encode = context.substrate.createType("LitentryValidationData", data).toHex() + const identity_ciphertext = encryptWithTeeShieldingKey(context.teeShieldingKey, identity_encode).toString('hex') + const validation_ciphertext = encryptWithTeeShieldingKey(context.teeShieldingKey, validation_encode).toString('hex') + await context.substrate.tx.identityManagement.verifyIdentity(context.shard, `0x${identity_ciphertext}`, `0x${validation_ciphertext}`).signAndSend(signer) + if (listening) { + const event = await listenEncryptedEvents(context, aesKey, { + module: "identityManagement", + method: "identityVerified", + event: "IdentityVerified" + }) + const [who, _identity] = event.eventData; + return who + } + return undefined +} diff --git a/tee-worker/ts-tests/package.json b/tee-worker/ts-tests/package.json new file mode 100644 index 0000000000..ebe13e5bba --- /dev/null +++ b/tee-worker/ts-tests/package.json @@ -0,0 +1,27 @@ +{ + "license": "ISC", + "scripts": { + "test-identity": "mocha --exit --sort -r ts-node/register 'identity.test.ts'" + }, + "dependencies": { + "@polkadot/api": "^9.5.2", + "@polkadot/types": "^9.5.2", + "chai": "^4.3.6", + "micro-base58": "^0.5.1", + "mocha": "^10.1.0", + "mocha-steps": "^1.3.0", + "websocket-as-promised": "^2.0.1", + "ws": "^8.8.1" + }, + "devDependencies": { + "@types/chai": "^4.3.3", + "@types/mocha": "^10.0.0", + "@types/ws": "^8.5.3", + "ts-node": "^10.8.1", + "typescript": "^4.7.3" + }, + "compilerOptions": { + "allowSyntheticDefaultImports": true, + "esModuleInterop": true + } +} diff --git a/tee-worker/ts-tests/type-definitions.ts b/tee-worker/ts-tests/type-definitions.ts new file mode 100644 index 0000000000..967138545b --- /dev/null +++ b/tee-worker/ts-tests/type-definitions.ts @@ -0,0 +1,211 @@ +import {ApiPromise, Keyring} from "@polkadot/api"; +import {KeyObject} from "crypto"; +import {HexString} from "@polkadot/util/types"; +import WebSocketAsPromised = require("websocket-as-promised"); +import {KeyringPair} from "@polkadot/keyring/types"; + +export const teeTypes = { + WorkerRpcReturnString: { + vec: "Bytes" + }, + WorkerRpcReturnValue: { + value: 'Bytes', + do_watch: 'bool', + status: 'DirectRequestStatus', + }, + TrustedOperation: { + _enum: { + indirect_call: "(TrustedCallSigned)", + direct_call: "(TrustedCallSigned)", + get: "(Getter)", + } + }, + TrustedCallSigned: { + call: 'TrustedCall', + index: 'u32', + signature: 'MultiSignature', + }, + Getter: { + _enum: { + 'public': '(PublicGetter)', + 'trusted': '(TrustedGetterSigned)' + } + }, + PublicGetter: { + _enum: [ + 'some_value' + ] + }, + TrustedGetterSigned: { + getter: "TrustedGetter", + signature: "MultiSignature" + }, + + /// important + TrustedGetter: { + _enum: { + free_balance: '(AccountId)' + } + }, + /// important + TrustedCall: { + _enum: { + balance_set_balance: '(AccountId, AccountId, Balance, Balance)', + balance_transfer: '(AccountId, AccountId, Balance)', + balance_unshield: '(AccountId, AccountId, Balance, ShardIdentifier)', + } + }, + DirectRequestStatus: { + _enum: [ + //TODO support TrustedOperationStatus(TrustedOperationStatus) + 'Ok', 'TrustedOperationStatus', 'Error' + ] + }, + + /// identity + LitentryIdentity: { + web_type: "IdentityWebType", + handle: "IdentityHandle" + }, + IdentityWebType: { + _enum: { + Web2Identity: "Web2Network", + Web3Identity: "Web3Network" + } + }, + Web2Network: { + _enum: ["Twitter", "Discord", "Github"] + }, + Web3Network: { + _enum: { + Substrate: "SubstrateNetwork", + Evm: "EvmNetwork" + } + }, + SubstrateNetwork: { + _enum: ["Polkadot", "Kusama", "Litentry", "Litmus"] + }, + EvmNetwork: { + _enum: ['Ethereum', 'BSC'] + }, + IdentityHandle: { + _enum: { + Address32: '[u8;32]', + Address20: '[u8;20]', + PlainString: 'Vec' + } + }, + + /// Validation Data + LitentryValidationData: { + _enum: { + Web2Validation: "Web2ValidationData", + Web3Validation: "Web3ValidationData" + } + }, + Web2ValidationData: { + _enum: { + Twitter: "TwitterValidationData", + Discord: "DiscordValidationData" + } + }, + TwitterValidationData: { + tweet_id: "Vec" + }, + DiscordValidationData: { + channel_id: "Vec", + message_id: "Vec", + guild_id: "Vec" + }, + Web3ValidationData: { + _enum: { + Substrate: "Web3CommonValidationData", + Evm: "Web3CommonValidationData" + } + }, + Web3CommonValidationData: { + message: "Vec", + signature: "IdentityMultiSignature" + }, + IdentityMultiSignature: { + _enum: {} + } +} + +export type WorkerRpcReturnValue = { + value: HexString + do_watch: boolean + status: string +} + +export type WorkerRpcReturnString = { + vec: string +} + +export type PubicKeyJson = { + n: Uint8Array, + e: Uint8Array +} + + +export type IntegrationTestContext = { + tee: WebSocketAsPromised, + substrate: ApiPromise, + teeShieldingKey: KeyObject, + shard: HexString + defaultSigner: KeyringPair +} + +export class AESOutput { + ciphertext?: Uint8Array + aad?: Uint8Array + nonce?: Uint8Array +} + +export type LitentryIdentity = { + web_type: IdentityWebType, + handle: IdentityHandle, +} + +export type IdentityWebType = { + Web2Identity?: Web2Network + Web3Identity?: Web3Network +} + +export type IdentityHandle = { + Address32: `0x${string}`, + Address20: `0x${string},` + PlainString: `0x${string}` +} + +export type LitentryValidationData = { + Web2Validation?: Web2ValidationData, + Web3Validation?: string, +} + +export type Web2ValidationData = { + Twitter?: TwitterValidationData, + Discord?: DiscordValidationData +} + +export type TwitterValidationData = { + tweet_id: HexString +} + +export type DiscordValidationData = { + channel_id: HexString, + message_id: HexString, + guild_id: HexString +} + +// export type DiscordValidationData = {} + +export type Web3Network = { + Substrate: SubstrateNetwork + Evm: EvmNetwork +} + +export type Web2Network = "Twitter" | "Discord" | "Github" +export type SubstrateNetwork = "Polkadot" | "Kusama" | "Litentry" | "Litmus" +export type EvmNetwork = 'Ethereum' | 'BSC' + diff --git a/tee-worker/ts-tests/utils.ts b/tee-worker/ts-tests/utils.ts new file mode 100644 index 0000000000..edce926c54 --- /dev/null +++ b/tee-worker/ts-tests/utils.ts @@ -0,0 +1,247 @@ +import WebSocketAsPromised = require("websocket-as-promised"); +import WebSocket = require("ws"); +import Options = require("websocket-as-promised/types/options"); +import {ApiPromise, Keyring, WsProvider} from "@polkadot/api"; +import {StorageKey, Vec} from "@polkadot/types"; +import { + AESOutput, + IntegrationTestContext, + LitentryIdentity, + PubicKeyJson, + teeTypes, + WorkerRpcReturnString, + WorkerRpcReturnValue +} from "./type-definitions"; +import {blake2AsHex, cryptoWaitReady} from "@polkadot/util-crypto"; +import {KeyringPair} from "@polkadot/keyring/types"; +import {Codec} from "@polkadot/types/types"; +import {HexString} from "@polkadot/util/types"; +import {hexToU8a, u8aToHex} from "@polkadot/util"; +import {KeyObject} from "crypto"; +import {EventRecord} from "@polkadot/types/interfaces"; +import {after, before, describe} from "mocha"; + +const base58 = require('micro-base58'); +const crypto = require("crypto"); +// in order to handle self-signed certificates we need to turn off the validation +// TODO add self signed certificate ?? +process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0"; + +export function sleep(secs: number) { + return new Promise((resolve) => { + setTimeout(resolve, secs * 1000); + }); +} + +export async function sendRequest(wsClient: WebSocketAsPromised, request: any, api: ApiPromise): Promise { + const resp = await wsClient.sendRequest(request, {requestId: 1, timeout: 6000}) + const resp_json = api.createType("WorkerRpcReturnValue", resp.result).toJSON() as WorkerRpcReturnValue + return resp_json +} + +export async function getTEEShieldingKey(wsClient: WebSocketAsPromised, api: ApiPromise): Promise { + let request = {jsonrpc: "2.0", method: "author_getShieldingKey", params: [], id: 1}; + let respJSON = await sendRequest(wsClient, request, api) + + const pubKeyHex = api.createType("WorkerRpcReturnString", respJSON.value).toJSON() as WorkerRpcReturnString + let chunk = Buffer.from(pubKeyHex.vec.slice(2), 'hex'); + let pubKeyJSON = JSON.parse(chunk.toString("utf-8")) as PubicKeyJson + + return crypto.createPublicKey({ + key: { + "alg": "RSA-OAEP-256", + "kty": "RSA", + "use": "enc", + "n": Buffer.from(pubKeyJSON.n.reverse()).toString('base64url'), + "e": Buffer.from(pubKeyJSON.e.reverse()).toString('base64url') + }, + format: 'jwk', + }) +} + +export async function initIntegrationTestContext(workerEndpoint: string, substrateEndpoint: string): Promise { + const provider = new WsProvider(substrateEndpoint) + const api = await ApiPromise.create({ + provider, types: teeTypes + }) + await cryptoWaitReady() + const keys = await api.query.sidechain.workerForShard.entries() as [StorageKey, Codec][]; + let shard = "" + for (let i = 0; i < keys.length; i++) { + //TODO shard may be different from mr_enclave. The default value of shard is mr_enclave + shard = keys[i][0].args[0].toHex() + console.log("query worker shard: ", shard) + break + } + if (shard == "") { + throw new Error("shard not found") + } + + // const endpoint = "wss://localhost:2000" + const wsp = new WebSocketAsPromised(workerEndpoint, { + createWebSocket: (url: any) => new WebSocket(url), + extractMessageData: (event: any) => event, // <- this is important + packMessage: (data: any) => JSON.stringify(data), + unpackMessage: (data: string | ArrayBuffer | Blob) => JSON.parse(data.toString()), + attachRequestId: (data: any, requestId: string | number) => Object.assign({id: requestId}, data), // attach requestId to message as `id` field + extractRequestId: (data: any) => data && data.id, // read requestId from message `id` field + }) + await wsp.open() + + const keyring = new Keyring({type: 'sr25519'}); + + + const teeShieldingKey = await getTEEShieldingKey(wsp, api) + return { + tee: wsp, + substrate: api, + teeShieldingKey, + shard, + defaultSigner: keyring.addFromUri('//Alice', {name: 'Alice'}) + } +} + +export async function listenEncryptedEvents(context: IntegrationTestContext, aesKey: HexString, filterObj: { "module": string, "method": string, "event": string }) { + return new Promise<{ eventData: HexString[] }>(async (resolve, reject) => { + let startBlock = 0; + let timeout = 10; // 10 block number timeout + const unsubscribe = await context.substrate.rpc.chain.subscribeNewHeads(async (header) => { + const currentBlockNumber = header.number.toNumber() + if (startBlock == 0) startBlock = currentBlockNumber; + if (currentBlockNumber > startBlock + timeout) { + reject("timeout"); + return + } + console.log(`Chain is at block: #${header.number}`); + const signedBlock = await context.substrate.rpc.chain.getBlock(header.hash); + const allEvents = await context.substrate.query.system.events.at(header.hash) as Vec; + signedBlock.block.extrinsics.forEach((ex, index) => { + if (!(ex.method.section === filterObj.module && ex.method.method === filterObj.method)) { + return; + } + allEvents + .filter(({phase, event}) => { + return phase.isApplyExtrinsic && phase.asApplyExtrinsic.eq(index) + && event.section == filterObj.module && event.method == filterObj.event + }) + .forEach(({event}) => { + // const eventData = event.data as AESOutput; + const data = event.data as AESOutput[] + const eventData: HexString[] = [] + for (let i = 0; i < data.length; i++) { + eventData.push(decryptWithAES(aesKey, data[i])) + } + resolve({eventData}) + unsubscribe() + return + }); + }); + }); + }); +} + + +// export function encryptWithAES(key: HexString, plaintext: HexString): [Buffer, Buffer, Buffer] { +// console.log("plaintext: ", plaintext) +// const iv = new Buffer(crypto.randomBytes(12), 'utf8'); +// const secretKey = crypto.createSecretKey(hexToU8a(key)) +// console.log(secretKey) +// const cipher = crypto.createCipheriv('aes-256-gcm', secretKey, iv); +// cipher.setAAD(Buffer.from('', 'hex')) +// let enc1 = cipher.update(hexToU8a(plaintext)); +// let enc2 = cipher.final(); +// console.log('111', enc1.toString('hex'), enc2.toString('hex')) +// +// const decipher = crypto.createDecipheriv('aes-256-gcm', secretKey, iv); +// decipher.setAuthTag(cipher.getAuthTag()) +// console.log(decipher.update(enc1).toString('hex')) +// console.log(decipher.final().toString('hex')) +// console.log(`0x${iv.toString('hex')}`) +// return [Buffer.concat([enc1, enc2]), iv, cipher.getAuthTag()]; +// } + +export function decryptWithAES(key: HexString, aesOutput: AESOutput): HexString { + const secretKey = crypto.createSecretKey(hexToU8a(key)) + const tagSize = 16 + const ciphertext = aesOutput.ciphertext ? aesOutput.ciphertext : hexToU8a("0x") + const initialization_vector = aesOutput.nonce ? aesOutput.nonce : hexToU8a("0x") + const aad = aesOutput.aad ? aesOutput.aad : hexToU8a("0x") + + // notice!!! extract author_tag from ciphertext + // maybe this code only works with rust aes encryption + const authorTag = ciphertext.subarray(ciphertext.length - tagSize) + const decipher = crypto.createDecipheriv('aes-256-gcm', secretKey, initialization_vector); + decipher.setAAD(aad); + decipher.setAuthTag(authorTag) + + let part1 = decipher.update(ciphertext.subarray(0, ciphertext.length - tagSize), undefined, 'hex'); + let part2 = decipher.final('hex') + return `0x${part1 + part2}`; +} + + +export async function createTrustedCallSigned(api: ApiPromise, trustedCall: [string, string], account: KeyringPair, mrenclave: string, shard: string, nonce: Codec, params: Array) { + const [variant, argType] = trustedCall; + const call = api.createType('TrustedCall', { + [variant]: api.createType(argType, params) + }); + const payload = Uint8Array.from([...call.toU8a(), ...nonce.toU8a(), ...base58.decode(mrenclave), ...hexToU8a(shard)]); + const signature = api.createType('MultiSignature', { + "Sr25519": u8aToHex(account.sign(payload)) + }) + return api.createType('TrustedCallSigned', { + call: call, + index: nonce, + signature: signature + }); +} + +export function encryptWithTeeShieldingKey(teeShieldingKey: KeyObject, plaintext: HexString): Buffer { + return crypto.publicEncrypt({ + key: teeShieldingKey, + padding: crypto.constants.RSA_PKCS1_OAEP_PADDING, + oaepHash: 'sha256' + }, hexToU8a(plaintext)) +} + +// + + +export function generateVerificationMessage(context: IntegrationTestContext, challengeCode: Uint8Array, signerAddress: Uint8Array, identity: LitentryIdentity): HexString { + const encode = context.substrate.createType("LitentryIdentity", identity).toU8a() + const msg = Buffer.concat([challengeCode, signerAddress, encode]) + // return encryptWithTeeShieldingKey(context.teeShieldingKey, `0x${msg.toString('hex')}`) + return blake2AsHex(msg, 256) +} + +export function describeLitentry(title: string, cb: (context: IntegrationTestContext) => void) { + describe(title, function () { + // Set timeout to 6000 seconds + this.timeout(6000000); + let context: IntegrationTestContext = { + defaultSigner: {} as KeyringPair, + shard: "0x11" as HexString, + substrate: {} as ApiPromise, + tee: {} as WebSocketAsPromised, + teeShieldingKey: {} as KeyObject + }; + + before('Starting Litentry(parachain&tee)', async function () { + // // For test locally + // const tmp = await initIntegrationTestContext("wss://localhost:2000", "ws://localhost:9946") + + // For docker service test + // Frist param: wss://integritee-worker:trusted-worker-port + const tmp = await initIntegrationTestContext("wss://integritee-worker-1:2011", "ws://integritee-node:9912") + + context.defaultSigner = tmp.defaultSigner + context.shard = tmp.shard + context.substrate = tmp.substrate + context.tee = tmp.tee + context.teeShieldingKey = tmp.teeShieldingKey + }); + + after(async function () { + }); + cb(context); + }); +} + diff --git a/tee-worker/ts-tests/yarn.lock b/tee-worker/ts-tests/yarn.lock new file mode 100644 index 0000000000..ac8994baf2 --- /dev/null +++ b/tee-worker/ts-tests/yarn.lock @@ -0,0 +1,1721 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@babel/runtime@^7.18.9": + version "7.19.0" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.19.0.tgz#22b11c037b094d27a8a2504ea4dcff00f50e2259" + integrity sha512-eR8Lo9hnDS7tqkO7NsV+mKvCmv5boaXFSZ70DnfhcgiEne8hv9oCEd36Klw74EtizEqLsy4YnW8UWwpBVolHZA== + dependencies: + regenerator-runtime "^0.13.4" + +"@babel/runtime@^7.19.4": + version "7.19.4" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.19.4.tgz#a42f814502ee467d55b38dd1c256f53a7b885c78" + integrity sha512-EXpLCrk55f+cYqmHsSR+yD/0gAIMxxA9QK9lnQWzhMCvt+YmoBN7Zx94s++Kv0+unHk39vxNO8t+CMA2WSS3wA== + dependencies: + regenerator-runtime "^0.13.4" + +"@cspotcode/source-map-support@^0.8.0": + version "0.8.1" + resolved "https://registry.yarnpkg.com/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz#00629c35a688e05a88b1cda684fb9d5e73f000a1" + integrity sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw== + dependencies: + "@jridgewell/trace-mapping" "0.3.9" + +"@jridgewell/resolve-uri@^3.0.3": + version "3.1.0" + resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz#2203b118c157721addfe69d47b70465463066d78" + integrity sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w== + +"@jridgewell/sourcemap-codec@^1.4.10": + version "1.4.14" + resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz#add4c98d341472a289190b424efbdb096991bb24" + integrity sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw== + +"@jridgewell/trace-mapping@0.3.9": + version "0.3.9" + resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz#6534fd5933a53ba7cbf3a17615e273a0d1273ff9" + integrity sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ== + dependencies: + "@jridgewell/resolve-uri" "^3.0.3" + "@jridgewell/sourcemap-codec" "^1.4.10" + +"@noble/hashes@1.1.3": + version "1.1.3" + resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.1.3.tgz#360afc77610e0a61f3417e497dcf36862e4f8111" + integrity sha512-CE0FCR57H2acVI5UOzIGSSIYxZ6v/HOhDR0Ro9VLyhnzLwx0o8W1mmgaqlEUx4049qJDlIBRztv5k+MM8vbO3A== + +"@noble/secp256k1@1.7.0": + version "1.7.0" + resolved "https://registry.yarnpkg.com/@noble/secp256k1/-/secp256k1-1.7.0.tgz#d15357f7c227e751d90aa06b05a0e5cf993ba8c1" + integrity sha512-kbacwGSsH/CTout0ZnZWxnW1B+jH/7r/WAAKLBtrRJ/+CUH7lgmQzl3GTrQua3SGKWNSDsS6lmjnDpIJ5Dxyaw== + +"@polkadot/api-augment@9.5.2": + version "9.5.2" + resolved "https://registry.yarnpkg.com/@polkadot/api-augment/-/api-augment-9.5.2.tgz#55168dd112517028fea5f2ab9c54ea627e43ac3a" + integrity sha512-dH6QMY8Z3zI6CrgSU3eSe6f0KWDb5PYGztg/FXGPrjh7Vjic7syWZ1LD6zaHJAFWDp80BEdEXfqr4lConrCKGg== + dependencies: + "@babel/runtime" "^7.19.4" + "@polkadot/api-base" "9.5.2" + "@polkadot/rpc-augment" "9.5.2" + "@polkadot/types" "9.5.2" + "@polkadot/types-augment" "9.5.2" + "@polkadot/types-codec" "9.5.2" + "@polkadot/util" "^10.1.11" + +"@polkadot/api-base@9.5.2": + version "9.5.2" + resolved "https://registry.yarnpkg.com/@polkadot/api-base/-/api-base-9.5.2.tgz#ac0a6b5546a54bcc753ac55c9f033caa9f8b4e5c" + integrity sha512-BBsH9SLB1FHgjdiU32cZX1puL3Eh8IjOJHjRsO/5SdttciQhF5g/u/m/mM/55qnlXmffI9s2Jre18G0XtVU9Aw== + dependencies: + "@babel/runtime" "^7.19.4" + "@polkadot/rpc-core" "9.5.2" + "@polkadot/types" "9.5.2" + "@polkadot/util" "^10.1.11" + rxjs "^7.5.7" + +"@polkadot/api-derive@9.5.2": + version "9.5.2" + resolved "https://registry.yarnpkg.com/@polkadot/api-derive/-/api-derive-9.5.2.tgz#c0412cfc13fa71f93b315d126b12b5ab38e6438c" + integrity sha512-kWn12dlqfIES1trNLd3O1i2qa4T97v/co1VMCgVstICwCt3+mGZgpxkMqQqPiWHagKEVeBNoAn+h8eOiQlbujA== + dependencies: + "@babel/runtime" "^7.19.4" + "@polkadot/api" "9.5.2" + "@polkadot/api-augment" "9.5.2" + "@polkadot/api-base" "9.5.2" + "@polkadot/rpc-core" "9.5.2" + "@polkadot/types" "9.5.2" + "@polkadot/types-codec" "9.5.2" + "@polkadot/util" "^10.1.11" + "@polkadot/util-crypto" "^10.1.11" + rxjs "^7.5.7" + +"@polkadot/api@9.5.2", "@polkadot/api@^9.5.2": + version "9.5.2" + resolved "https://registry.yarnpkg.com/@polkadot/api/-/api-9.5.2.tgz#cef83928e47c393fbebf2788bc86841b6ab37a41" + integrity sha512-iEF/E8vQan3fHmIEl3bX7Yn/1jQLlvSDwPOxiQdj4tIcF36HX6vCbkdhQKRif0CNYES58TA9EKFiCNg81k+kXw== + dependencies: + "@babel/runtime" "^7.19.4" + "@polkadot/api-augment" "9.5.2" + "@polkadot/api-base" "9.5.2" + "@polkadot/api-derive" "9.5.2" + "@polkadot/keyring" "^10.1.11" + "@polkadot/rpc-augment" "9.5.2" + "@polkadot/rpc-core" "9.5.2" + "@polkadot/rpc-provider" "9.5.2" + "@polkadot/types" "9.5.2" + "@polkadot/types-augment" "9.5.2" + "@polkadot/types-codec" "9.5.2" + "@polkadot/types-create" "9.5.2" + "@polkadot/types-known" "9.5.2" + "@polkadot/util" "^10.1.11" + "@polkadot/util-crypto" "^10.1.11" + eventemitter3 "^4.0.7" + rxjs "^7.5.7" + +"@polkadot/keyring@^10.1.11": + version "10.1.11" + resolved "https://registry.yarnpkg.com/@polkadot/keyring/-/keyring-10.1.11.tgz#a3fed011b0c8826ea2097e04f7189e9be66fbf98" + integrity sha512-Nv8cZaOA/KbdslDMTklJ58+y+UPpic3+oMQoozuq48Ccjv7WeW2BX47XM/RNE8nYFg6EHa6Whfm4IFaFb8s7ag== + dependencies: + "@babel/runtime" "^7.19.4" + "@polkadot/util" "10.1.11" + "@polkadot/util-crypto" "10.1.11" + +"@polkadot/networks@10.1.11", "@polkadot/networks@^10.1.11": + version "10.1.11" + resolved "https://registry.yarnpkg.com/@polkadot/networks/-/networks-10.1.11.tgz#96a5d6c80228f4beada9154cca0f60a63198e7f4" + integrity sha512-4FfOVETXwh6PL6wd6fYJMkRSQKm+xUw3vR5rHqcAnB696FpMFPPErc6asgZ9lYMyzNJRY3yG86HQpFhtCv1nGA== + dependencies: + "@babel/runtime" "^7.19.4" + "@polkadot/util" "10.1.11" + "@substrate/ss58-registry" "^1.33.0" + +"@polkadot/rpc-augment@9.5.2": + version "9.5.2" + resolved "https://registry.yarnpkg.com/@polkadot/rpc-augment/-/rpc-augment-9.5.2.tgz#739cc3ed2f86f4318432e38381a2cc780dc64f1e" + integrity sha512-QAcunC7p/T4xy6e4m0Q1c9tiVYxnm+S9o10tmtx0K4qXzrc/4I2/tsw3nEGi3BzJhvMpFondSQGcJ3gyLwpmVA== + dependencies: + "@babel/runtime" "^7.19.4" + "@polkadot/rpc-core" "9.5.2" + "@polkadot/types" "9.5.2" + "@polkadot/types-codec" "9.5.2" + "@polkadot/util" "^10.1.11" + +"@polkadot/rpc-core@9.5.2": + version "9.5.2" + resolved "https://registry.yarnpkg.com/@polkadot/rpc-core/-/rpc-core-9.5.2.tgz#1a00868038b6c07fe8f58bd0a6cc9519d14001cc" + integrity sha512-4PbNz0GEp3FXYOnsS7mDHZy9DNVBOl56fq8vs09rLkEkrrvGkHmCvabEEWL7OPbwBzwzsCxdgI+IdkVTUKXPkQ== + dependencies: + "@babel/runtime" "^7.19.4" + "@polkadot/rpc-augment" "9.5.2" + "@polkadot/rpc-provider" "9.5.2" + "@polkadot/types" "9.5.2" + "@polkadot/util" "^10.1.11" + rxjs "^7.5.7" + +"@polkadot/rpc-provider@9.5.2": + version "9.5.2" + resolved "https://registry.yarnpkg.com/@polkadot/rpc-provider/-/rpc-provider-9.5.2.tgz#3e38ea4c3639180f12270b6fe8cbcabf728aaf1d" + integrity sha512-Sn2jfvAsvQcl35o0up8JR/XbDMS/3YVDEN2sFuzXtiD77W2njukItbZT+BolfAW+biAUs3bNomump5k/YLiLKg== + dependencies: + "@babel/runtime" "^7.19.4" + "@polkadot/keyring" "^10.1.11" + "@polkadot/types" "9.5.2" + "@polkadot/types-support" "9.5.2" + "@polkadot/util" "^10.1.11" + "@polkadot/util-crypto" "^10.1.11" + "@polkadot/x-fetch" "^10.1.11" + "@polkadot/x-global" "^10.1.11" + "@polkadot/x-ws" "^10.1.11" + "@substrate/connect" "0.7.14" + eventemitter3 "^4.0.7" + mock-socket "^9.1.5" + nock "^13.2.9" + +"@polkadot/types-augment@9.5.2": + version "9.5.2" + resolved "https://registry.yarnpkg.com/@polkadot/types-augment/-/types-augment-9.5.2.tgz#d9e77756b0e36455d708f5af8265ef011ddf8d91" + integrity sha512-LDJdv/84sECwA0R5lK85/orxjoozJe3+2jeLjRiKr8S6qm9XRfz0wLCSF866kpSGBZ4B1dYBUhzjoSu95y2Jug== + dependencies: + "@babel/runtime" "^7.19.4" + "@polkadot/types" "9.5.2" + "@polkadot/types-codec" "9.5.2" + "@polkadot/util" "^10.1.11" + +"@polkadot/types-codec@9.5.2": + version "9.5.2" + resolved "https://registry.yarnpkg.com/@polkadot/types-codec/-/types-codec-9.5.2.tgz#345c38ccef17651b8cabd159a42810893b5e7e44" + integrity sha512-FJPjE3ceTGTcadeC8d5C+aSR8SLKuQrXKIBmMNBky+WwzEo0vufRqxFWcPLxAOEeeUPgBXS967tP15+UU4psGA== + dependencies: + "@babel/runtime" "^7.19.4" + "@polkadot/util" "^10.1.11" + "@polkadot/x-bigint" "^10.1.11" + +"@polkadot/types-create@9.5.2": + version "9.5.2" + resolved "https://registry.yarnpkg.com/@polkadot/types-create/-/types-create-9.5.2.tgz#a85dcb794ea11e5d528baa34b65e57cfafc905cf" + integrity sha512-YbplL8K0LqUEHoV3FgZ5B83oVV67KGbLXsWHVVaUZBPsmtXJXrbBfSyJgl/80I2n4lXEBmg3sFAYMbaSTvL05A== + dependencies: + "@babel/runtime" "^7.19.4" + "@polkadot/types-codec" "9.5.2" + "@polkadot/util" "^10.1.11" + +"@polkadot/types-known@9.5.2": + version "9.5.2" + resolved "https://registry.yarnpkg.com/@polkadot/types-known/-/types-known-9.5.2.tgz#a71fd08932b1643bbf346321472ed48ab1ade215" + integrity sha512-iNaGOF6dGiTvy3Ns8Z7WNjYD1SGnZiapDAKPH4brPuJqMpN6/FxYpfPSSOKx+IJEamsdINcaggb87eWyPxH8CA== + dependencies: + "@babel/runtime" "^7.19.4" + "@polkadot/networks" "^10.1.11" + "@polkadot/types" "9.5.2" + "@polkadot/types-codec" "9.5.2" + "@polkadot/types-create" "9.5.2" + "@polkadot/util" "^10.1.11" + +"@polkadot/types-support@9.5.2": + version "9.5.2" + resolved "https://registry.yarnpkg.com/@polkadot/types-support/-/types-support-9.5.2.tgz#f2990d19cbd78c24e5b7116466fb1d89f93a8ca7" + integrity sha512-Zdbl5fvGQjUkyE1r67vhyPEqLUwlZ35GCnkoobY9MgN6gylhSjNue/shpG4uGsEjWVQL7GkFkrPiwtzDArVilg== + dependencies: + "@babel/runtime" "^7.19.4" + "@polkadot/util" "^10.1.11" + +"@polkadot/types@9.5.2", "@polkadot/types@^9.5.2": + version "9.5.2" + resolved "https://registry.yarnpkg.com/@polkadot/types/-/types-9.5.2.tgz#33ab2caea08f084141a01038adbe53ed69ab7d9c" + integrity sha512-6C5xzOrMK+fu0JMOlSO+8dPDhpwKPOaKMv3v5BMvBEWtDNKM81/QQoAoYT7DSVXq/V16icSFxPs9IWC+6Qq5ag== + dependencies: + "@babel/runtime" "^7.19.4" + "@polkadot/keyring" "^10.1.11" + "@polkadot/types-augment" "9.5.2" + "@polkadot/types-codec" "9.5.2" + "@polkadot/types-create" "9.5.2" + "@polkadot/util" "^10.1.11" + "@polkadot/util-crypto" "^10.1.11" + rxjs "^7.5.7" + +"@polkadot/util-crypto@10.1.11", "@polkadot/util-crypto@^10.1.11": + version "10.1.11" + resolved "https://registry.yarnpkg.com/@polkadot/util-crypto/-/util-crypto-10.1.11.tgz#e59bdc8e1e2bd98a115e2e2ed45461e68a14a48c" + integrity sha512-wG63frIMAR5T/HXGM0SFNzZZdk7qDBsfLXfn6PIZiXCCCsdEYPzS5WltB7fkhicYpbePJ7VgdCAddj1l4IcGyg== + dependencies: + "@babel/runtime" "^7.19.4" + "@noble/hashes" "1.1.3" + "@noble/secp256k1" "1.7.0" + "@polkadot/networks" "10.1.11" + "@polkadot/util" "10.1.11" + "@polkadot/wasm-crypto" "^6.3.1" + "@polkadot/x-bigint" "10.1.11" + "@polkadot/x-randomvalues" "10.1.11" + "@scure/base" "1.1.1" + ed2curve "^0.3.0" + tweetnacl "^1.0.3" + +"@polkadot/util@10.1.11", "@polkadot/util@^10.1.11": + version "10.1.11" + resolved "https://registry.yarnpkg.com/@polkadot/util/-/util-10.1.11.tgz#22bcdabbd7a0d266417f6569cc655f516d371a82" + integrity sha512-6m51lw6g6ilqO/k4BQY7rD0lYM9NCnC4FiM7CEEUc7j8q86qxdcZ88zdNldkhNsTIQnfmCtkK3GRzZW6VYrbUw== + dependencies: + "@babel/runtime" "^7.19.4" + "@polkadot/x-bigint" "10.1.11" + "@polkadot/x-global" "10.1.11" + "@polkadot/x-textdecoder" "10.1.11" + "@polkadot/x-textencoder" "10.1.11" + "@types/bn.js" "^5.1.1" + bn.js "^5.2.1" + +"@polkadot/wasm-bridge@6.3.1": + version "6.3.1" + resolved "https://registry.yarnpkg.com/@polkadot/wasm-bridge/-/wasm-bridge-6.3.1.tgz#439fa78e80947a7cb695443e1f64b25c30bb1487" + integrity sha512-1TYkHsb9AEFhU9uZj3biEnN2yKQNzdrwSjiTvfCYnt97pnEkKsZI6cku+YPZQv5w/x9CQa5Yua9e2DVVZSivGA== + dependencies: + "@babel/runtime" "^7.18.9" + +"@polkadot/wasm-crypto-asmjs@6.3.1": + version "6.3.1" + resolved "https://registry.yarnpkg.com/@polkadot/wasm-crypto-asmjs/-/wasm-crypto-asmjs-6.3.1.tgz#e8f469c9cf4a7709c8131a96f857291953f3e30a" + integrity sha512-zbombRfA5v/mUWQQhgg2YwaxhRmxRIrvskw65x+lruax3b6xPBFDs7yplopiJU3r8h2pTgQvX/DUksvqz2TCRQ== + dependencies: + "@babel/runtime" "^7.18.9" + +"@polkadot/wasm-crypto-init@6.3.1": + version "6.3.1" + resolved "https://registry.yarnpkg.com/@polkadot/wasm-crypto-init/-/wasm-crypto-init-6.3.1.tgz#b590220c53c94b9a54d5dc236d0cbe943db76706" + integrity sha512-9yaUBcu+snwjJLmPPGl3cyGRQ1afyFGm16qzTM0sgG/ZCfUlK4uk8KWZe+sBUKgoxb2oXY7Y4WklKgQI1YBdfw== + dependencies: + "@babel/runtime" "^7.18.9" + "@polkadot/wasm-bridge" "6.3.1" + "@polkadot/wasm-crypto-asmjs" "6.3.1" + "@polkadot/wasm-crypto-wasm" "6.3.1" + +"@polkadot/wasm-crypto-wasm@6.3.1": + version "6.3.1" + resolved "https://registry.yarnpkg.com/@polkadot/wasm-crypto-wasm/-/wasm-crypto-wasm-6.3.1.tgz#67f720e7f9694fef096abe9d60abbac02e032383" + integrity sha512-idSlzKGVzCfeCMRHsacRvqwojSaTadFxL/Dbls4z1thvfa3U9Ku0d2qVtlwg7Hj+tYWDiuP8Kygs+6bQwfs0XA== + dependencies: + "@babel/runtime" "^7.18.9" + "@polkadot/wasm-util" "6.3.1" + +"@polkadot/wasm-crypto@^6.3.1": + version "6.3.1" + resolved "https://registry.yarnpkg.com/@polkadot/wasm-crypto/-/wasm-crypto-6.3.1.tgz#63f5798aca2b2ff0696f190e6862d9781d8f280c" + integrity sha512-OO8h0qeVkqp4xYZaRVl4iuWOEtq282pNBHDKb6SOJuI2g59eWGcKh4EQU9Me2VP6qzojIqptrkrVt7KQXC68gA== + dependencies: + "@babel/runtime" "^7.18.9" + "@polkadot/wasm-bridge" "6.3.1" + "@polkadot/wasm-crypto-asmjs" "6.3.1" + "@polkadot/wasm-crypto-init" "6.3.1" + "@polkadot/wasm-crypto-wasm" "6.3.1" + "@polkadot/wasm-util" "6.3.1" + +"@polkadot/wasm-util@6.3.1": + version "6.3.1" + resolved "https://registry.yarnpkg.com/@polkadot/wasm-util/-/wasm-util-6.3.1.tgz#439ebb68a436317af388ed6438b8f879df3afcda" + integrity sha512-12oAv5J7Yoc9m6jixrSaQCxpOkWOyzHx3DMC8qmLjRiwdBWxqLmImOVRVnFsbaxqSbhBIHRuJphVxWE+GZETDg== + dependencies: + "@babel/runtime" "^7.18.9" + +"@polkadot/x-bigint@10.1.11", "@polkadot/x-bigint@^10.1.11": + version "10.1.11" + resolved "https://registry.yarnpkg.com/@polkadot/x-bigint/-/x-bigint-10.1.11.tgz#7d62ce10cccd55b86a415342db95b9feeb099776" + integrity sha512-TC4KZ+ni/SJhcf/LIwD49C/kwvACu0nCchETNO+sAfJ7COXZwHDUJXVXmwN5PgkQxwsWsKKuJmzR/Fi1bgMWnQ== + dependencies: + "@babel/runtime" "^7.19.4" + "@polkadot/x-global" "10.1.11" + +"@polkadot/x-fetch@^10.1.11": + version "10.1.11" + resolved "https://registry.yarnpkg.com/@polkadot/x-fetch/-/x-fetch-10.1.11.tgz#8f579bb166096c977acff91a40b3848fb5581900" + integrity sha512-WtyUr9itVD9BLnxCUloJ1iwrXOY/lnlEShEYKHcSm6MIHtbJolePd3v1+o5mOX+bdDbHXhPZnH8anCCqDNDRqg== + dependencies: + "@babel/runtime" "^7.19.4" + "@polkadot/x-global" "10.1.11" + "@types/node-fetch" "^2.6.2" + node-fetch "^3.2.10" + +"@polkadot/x-global@10.1.11", "@polkadot/x-global@^10.1.11": + version "10.1.11" + resolved "https://registry.yarnpkg.com/@polkadot/x-global/-/x-global-10.1.11.tgz#37dda3ef1cebfd14c68c69279ae6521957817866" + integrity sha512-bWz5gdcELy6+xfr27R1GE5MPX4nfVlchzHQH+DR6OBbSi9g/PeycQAvFB6IkTmP+YEbNNtIpxnSP37zoUaG3xw== + dependencies: + "@babel/runtime" "^7.19.4" + +"@polkadot/x-randomvalues@10.1.11": + version "10.1.11" + resolved "https://registry.yarnpkg.com/@polkadot/x-randomvalues/-/x-randomvalues-10.1.11.tgz#f9e088f8b400770d3e53ba9e0c0f0d464047f89e" + integrity sha512-V2V37f5hoM5B32eCpGw87Lwstin2+ArXhOZ8ENKncbQLXzbF9yTODueDoA5Vt0MJCs2CDP9cyiCYykcanqVkxg== + dependencies: + "@babel/runtime" "^7.19.4" + "@polkadot/x-global" "10.1.11" + +"@polkadot/x-textdecoder@10.1.11": + version "10.1.11" + resolved "https://registry.yarnpkg.com/@polkadot/x-textdecoder/-/x-textdecoder-10.1.11.tgz#314c79e27545a41fe0494a26196bf2dff5cfcb5d" + integrity sha512-QZqie04SR6pAj260PaLBfZUGXWKI357t4ROVJhpaj06qc1zrk1V8Mwkr49+WzjAPFEOqo70HWnzXmPNCH4dQiw== + dependencies: + "@babel/runtime" "^7.19.4" + "@polkadot/x-global" "10.1.11" + +"@polkadot/x-textencoder@10.1.11": + version "10.1.11" + resolved "https://registry.yarnpkg.com/@polkadot/x-textencoder/-/x-textencoder-10.1.11.tgz#23b18b3ffbc649572728aa37d7787432bb3a03b5" + integrity sha512-UX+uV9AbDID81waaG/NvTkkf7ZNVW7HSHaddgbWjQEVW2Ex4ByccBarY5jEi6cErEPKfzCamKhgXflu0aV9LWw== + dependencies: + "@babel/runtime" "^7.19.4" + "@polkadot/x-global" "10.1.11" + +"@polkadot/x-ws@^10.1.11": + version "10.1.11" + resolved "https://registry.yarnpkg.com/@polkadot/x-ws/-/x-ws-10.1.11.tgz#7431ad72064d56519d4293278f03ae97b9ea9271" + integrity sha512-EUbL/R1A/NxYf6Rnb1M7U9yeTuo5r4y2vcQllE5aBLaQ0cFnRykHzlmZlVX1E7O5uy3lYVdxWC7sNgxItIWkWA== + dependencies: + "@babel/runtime" "^7.19.4" + "@polkadot/x-global" "10.1.11" + "@types/websocket" "^1.0.5" + websocket "^1.0.34" + +"@scure/base@1.1.1": + version "1.1.1" + resolved "https://registry.yarnpkg.com/@scure/base/-/base-1.1.1.tgz#ebb651ee52ff84f420097055f4bf46cfba403938" + integrity sha512-ZxOhsSyxYwLJj3pLZCefNitxsj093tb2vq90mp2txoYeBqbcjDjqFhyM8eUjq/uFm6zJ+mUuqxlS2FkuSY1MTA== + +"@substrate/connect-extension-protocol@^1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@substrate/connect-extension-protocol/-/connect-extension-protocol-1.0.1.tgz#fa5738039586c648013caa6a0c95c43265dbe77d" + integrity sha512-161JhCC1csjH3GE5mPLEd7HbWtwNSPJBg3p1Ksz9SFlTzj/bgEwudiRN2y5i0MoLGCIJRYKyKGMxVnd29PzNjg== + +"@substrate/connect@0.7.14": + version "0.7.14" + resolved "https://registry.yarnpkg.com/@substrate/connect/-/connect-0.7.14.tgz#c090e952e9cdd93185a94d24fbc424ea20fe7bbe" + integrity sha512-uW5uBmihpivshmmmw+rsg7qOV0KqVSep4rWOXFMP8aFQinvmqw4JqxP21og4H/7JZxttYUBFQVsdtXHGKJ0aVQ== + dependencies: + "@substrate/connect-extension-protocol" "^1.0.1" + "@substrate/smoldot-light" "0.6.34" + eventemitter3 "^4.0.7" + +"@substrate/smoldot-light@0.6.34": + version "0.6.34" + resolved "https://registry.yarnpkg.com/@substrate/smoldot-light/-/smoldot-light-0.6.34.tgz#273dba622102281fd0fdb0e375198bff2ec584c3" + integrity sha512-+HK9MaJ0HelJmpf4YYR+salJ7dhVBltmhGlyz5l8OXS9DW18fe0Z2wxEo8P5kX9CUxlCXEb8J9JBRQAYBPHbwQ== + dependencies: + pako "^2.0.4" + ws "^8.8.1" + +"@substrate/ss58-registry@^1.33.0": + version "1.33.0" + resolved "https://registry.yarnpkg.com/@substrate/ss58-registry/-/ss58-registry-1.33.0.tgz#b93218fc86405769716b02f0ce5e61df221b37ae" + integrity sha512-DztMuMcEfu+tJrtIQIIp5gO8/XJZ8N8UwPObDCSNgrp7trtSkPJAUFB9qXaReXtN9UvTcVBMTWk6VPfFi04Wkg== + +"@tsconfig/node10@^1.0.7": + version "1.0.9" + resolved "https://registry.yarnpkg.com/@tsconfig/node10/-/node10-1.0.9.tgz#df4907fc07a886922637b15e02d4cebc4c0021b2" + integrity sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA== + +"@tsconfig/node12@^1.0.7": + version "1.0.11" + resolved "https://registry.yarnpkg.com/@tsconfig/node12/-/node12-1.0.11.tgz#ee3def1f27d9ed66dac6e46a295cffb0152e058d" + integrity sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag== + +"@tsconfig/node14@^1.0.0": + version "1.0.3" + resolved "https://registry.yarnpkg.com/@tsconfig/node14/-/node14-1.0.3.tgz#e4386316284f00b98435bf40f72f75a09dabf6c1" + integrity sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow== + +"@tsconfig/node16@^1.0.2": + version "1.0.3" + resolved "https://registry.yarnpkg.com/@tsconfig/node16/-/node16-1.0.3.tgz#472eaab5f15c1ffdd7f8628bd4c4f753995ec79e" + integrity sha512-yOlFc+7UtL/89t2ZhjPvvB/DeAr3r+Dq58IgzsFkOAvVC6NMJXmCGjbptdXdR9qsX7pKcTL+s87FtYREi2dEEQ== + +"@types/bn.js@^5.1.1": + version "5.1.1" + resolved "https://registry.yarnpkg.com/@types/bn.js/-/bn.js-5.1.1.tgz#b51e1b55920a4ca26e9285ff79936bbdec910682" + integrity sha512-qNrYbZqMx0uJAfKnKclPh+dTwK33KfLHYqtyODwd5HnXOjnkhc4qgn3BrK6RWyGZm5+sIFE7Q7Vz6QQtJB7w7g== + dependencies: + "@types/node" "*" + +"@types/chai@^4.3.3": + version "4.3.3" + resolved "https://registry.yarnpkg.com/@types/chai/-/chai-4.3.3.tgz#3c90752792660c4b562ad73b3fbd68bf3bc7ae07" + integrity sha512-hC7OMnszpxhZPduX+m+nrx+uFoLkWOMiR4oa/AZF3MuSETYTZmFfJAHqZEM8MVlvfG7BEUcgvtwoCTxBp6hm3g== + +"@types/mocha@^10.0.0": + version "10.0.0" + resolved "https://registry.yarnpkg.com/@types/mocha/-/mocha-10.0.0.tgz#3d9018c575f0e3f7386c1de80ee66cc21fbb7a52" + integrity sha512-rADY+HtTOA52l9VZWtgQfn4p+UDVM2eDVkMZT1I6syp0YKxW2F9v+0pbRZLsvskhQv/vMb6ZfCay81GHbz5SHg== + +"@types/node-fetch@^2.6.2": + version "2.6.2" + resolved "https://registry.yarnpkg.com/@types/node-fetch/-/node-fetch-2.6.2.tgz#d1a9c5fd049d9415dce61571557104dec3ec81da" + integrity sha512-DHqhlq5jeESLy19TYhLakJ07kNumXWjcDdxXsLUMJZ6ue8VZJj4kLPQVE/2mdHh3xZziNF1xppu5lwmS53HR+A== + dependencies: + "@types/node" "*" + form-data "^3.0.0" + +"@types/node@*": + version "18.7.21" + resolved "https://registry.yarnpkg.com/@types/node/-/node-18.7.21.tgz#63ee6688070e456325b6748dc492a7b948593871" + integrity sha512-rLFzK5bhM0YPyCoTC8bolBjMk7bwnZ8qeZUBslBfjZQou2ssJdWslx9CZ8DGM+Dx7QXQiiTVZ/6QO6kwtHkZCA== + +"@types/websocket@^1.0.5": + version "1.0.5" + resolved "https://registry.yarnpkg.com/@types/websocket/-/websocket-1.0.5.tgz#3fb80ed8e07f88e51961211cd3682a3a4a81569c" + integrity sha512-NbsqiNX9CnEfC1Z0Vf4mE1SgAJ07JnRYcNex7AJ9zAVzmiGHmjKFEk7O4TJIsgv2B1sLEb6owKFZrACwdYngsQ== + dependencies: + "@types/node" "*" + +"@types/ws@^8.5.3": + version "8.5.3" + resolved "https://registry.yarnpkg.com/@types/ws/-/ws-8.5.3.tgz#7d25a1ffbecd3c4f2d35068d0b283c037003274d" + integrity sha512-6YOoWjruKj1uLf3INHH7D3qTXwFfEsg1kf3c0uDdSBJwfa/llkwIjrAGV7j7mVgGNbzTQ3HiHKKDXl6bJPD97w== + dependencies: + "@types/node" "*" + +acorn-walk@^8.1.1: + version "8.2.0" + resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-8.2.0.tgz#741210f2e2426454508853a2f44d0ab83b7f69c1" + integrity sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA== + +acorn@^8.4.1: + version "8.8.0" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.8.0.tgz#88c0187620435c7f6015803f5539dae05a9dbea8" + integrity sha512-QOxyigPVrpZ2GXT+PFyZTl6TtOFc5egxHIP9IlQ+RbupQuX4RkT/Bee4/kQuC02Xkzg84JcT7oLYtDIQxp+v7w== + +ansi-colors@4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-4.1.1.tgz#cbb9ae256bf750af1eab344f229aa27fe94ba348" + integrity sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA== + +ansi-regex@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" + integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== + +ansi-styles@^4.0.0, ansi-styles@^4.1.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937" + integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== + dependencies: + color-convert "^2.0.1" + +anymatch@~3.1.2: + version "3.1.2" + resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.2.tgz#c0557c096af32f106198f4f4e2a383537e378716" + integrity sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg== + dependencies: + normalize-path "^3.0.0" + picomatch "^2.0.4" + +arg@^4.1.0: + version "4.1.3" + resolved "https://registry.yarnpkg.com/arg/-/arg-4.1.3.tgz#269fc7ad5b8e42cb63c896d5666017261c144089" + integrity sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA== + +argparse@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38" + integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== + +assertion-error@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/assertion-error/-/assertion-error-1.1.0.tgz#e60b6b0e8f301bd97e5375215bda406c85118c0b" + integrity sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw== + +asynckit@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" + integrity sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q== + +balanced-match@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" + integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== + +binary-extensions@^2.0.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.2.0.tgz#75f502eeaf9ffde42fc98829645be4ea76bd9e2d" + integrity sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA== + +bn.js@^5.2.1: + version "5.2.1" + resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-5.2.1.tgz#0bc527a6a0d18d0aa8d5b0538ce4a77dccfa7b70" + integrity sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ== + +brace-expansion@^1.1.7: + version "1.1.11" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" + integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== + dependencies: + balanced-match "^1.0.0" + concat-map "0.0.1" + +brace-expansion@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-2.0.1.tgz#1edc459e0f0c548486ecf9fc99f2221364b9a0ae" + integrity sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA== + dependencies: + balanced-match "^1.0.0" + +braces@~3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" + integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A== + dependencies: + fill-range "^7.0.1" + +browser-stdout@1.3.1: + version "1.3.1" + resolved "https://registry.yarnpkg.com/browser-stdout/-/browser-stdout-1.3.1.tgz#baa559ee14ced73452229bad7326467c61fabd60" + integrity sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw== + +bufferutil@^4.0.1: + version "4.0.6" + resolved "https://registry.yarnpkg.com/bufferutil/-/bufferutil-4.0.6.tgz#ebd6c67c7922a0e902f053e5d8be5ec850e48433" + integrity sha512-jduaYOYtnio4aIAyc6UbvPCVcgq7nYpVnucyxr6eCYg/Woad9Hf/oxxBRDnGGjPfjUm6j5O/uBWhIu4iLebFaw== + dependencies: + node-gyp-build "^4.3.0" + +call-bind@^1.0.0, call-bind@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.2.tgz#b1d4e89e688119c3c9a903ad30abb2f6a919be3c" + integrity sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA== + dependencies: + function-bind "^1.1.1" + get-intrinsic "^1.0.2" + +camelcase@^6.0.0: + version "6.3.0" + resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.3.0.tgz#5685b95eb209ac9c0c177467778c9c84df58ba9a" + integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA== + +chai@^4.3.6: + version "4.3.6" + resolved "https://registry.yarnpkg.com/chai/-/chai-4.3.6.tgz#ffe4ba2d9fa9d6680cc0b370adae709ec9011e9c" + integrity sha512-bbcp3YfHCUzMOvKqsztczerVgBKSsEijCySNlHHbX3VG1nskvqjz5Rfso1gGwD6w6oOV3eI60pKuMOV5MV7p3Q== + dependencies: + assertion-error "^1.1.0" + check-error "^1.0.2" + deep-eql "^3.0.1" + get-func-name "^2.0.0" + loupe "^2.3.1" + pathval "^1.1.1" + type-detect "^4.0.5" + +chalk@^4.1.0: + version "4.1.2" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" + integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== + dependencies: + ansi-styles "^4.1.0" + supports-color "^7.1.0" + +check-error@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/check-error/-/check-error-1.0.2.tgz#574d312edd88bb5dd8912e9286dd6c0aed4aac82" + integrity sha512-BrgHpW9NURQgzoNyjfq0Wu6VFO6D7IZEmJNdtgNqpzGG8RuNFHt2jQxWlAs4HMe119chBnv+34syEZtc6IhLtA== + +chnl@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/chnl/-/chnl-1.2.0.tgz#d818c95367767a0880508e7cc0b5b3503f58fd4c" + integrity sha512-g5gJb59edwCliFbX2j7G6sBfY4sX9YLy211yctONI2GRaiX0f2zIbKWmBm+sPqFNEpM7Ljzm7IJX/xrjiEbPrw== + +chokidar@3.5.3: + version "3.5.3" + resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.3.tgz#1cf37c8707b932bd1af1ae22c0432e2acd1903bd" + integrity sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw== + dependencies: + anymatch "~3.1.2" + braces "~3.0.2" + glob-parent "~5.1.2" + is-binary-path "~2.1.0" + is-glob "~4.0.1" + normalize-path "~3.0.0" + readdirp "~3.6.0" + optionalDependencies: + fsevents "~2.3.2" + +cliui@^7.0.2: + version "7.0.4" + resolved "https://registry.yarnpkg.com/cliui/-/cliui-7.0.4.tgz#a0265ee655476fc807aea9df3df8df7783808b4f" + integrity sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ== + dependencies: + string-width "^4.2.0" + strip-ansi "^6.0.0" + wrap-ansi "^7.0.0" + +color-convert@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" + integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== + dependencies: + color-name "~1.1.4" + +color-name@~1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" + integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== + +combined-stream@^1.0.8: + version "1.0.8" + resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" + integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg== + dependencies: + delayed-stream "~1.0.0" + +concat-map@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" + integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== + +create-require@^1.1.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/create-require/-/create-require-1.1.1.tgz#c1d7e8f1e5f6cfc9ff65f9cd352d37348756c333" + integrity sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ== + +d@1, d@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/d/-/d-1.0.1.tgz#8698095372d58dbee346ffd0c7093f99f8f9eb5a" + integrity sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA== + dependencies: + es5-ext "^0.10.50" + type "^1.0.1" + +data-uri-to-buffer@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/data-uri-to-buffer/-/data-uri-to-buffer-4.0.0.tgz#b5db46aea50f6176428ac05b73be39a57701a64b" + integrity sha512-Vr3mLBA8qWmcuschSLAOogKgQ/Jwxulv3RNE4FXnYWRGujzrRWQI4m12fQqRkwX06C0KanhLr4hK+GydchZsaA== + +debug@4.3.4, debug@^4.1.0: + version "4.3.4" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" + integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== + dependencies: + ms "2.1.2" + +debug@^2.2.0: + version "2.6.9" + resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" + integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== + dependencies: + ms "2.0.0" + +decamelize@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-4.0.0.tgz#aa472d7bf660eb15f3494efd531cab7f2a709837" + integrity sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ== + +deep-eql@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/deep-eql/-/deep-eql-3.0.1.tgz#dfc9404400ad1c8fe023e7da1df1c147c4b444df" + integrity sha512-+QeIQyN5ZuO+3Uk5DYh6/1eKO0m0YmJFGNmFHGACpf1ClL1nmlV/p4gNgbl2pJGxgXb4faqo6UE+M5ACEMyVcw== + dependencies: + type-detect "^4.0.0" + +define-properties@^1.1.3, define-properties@^1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.4.tgz#0b14d7bd7fbeb2f3572c3a7eda80ea5d57fb05b1" + integrity sha512-uckOqKcfaVvtBdsVkdPv3XjveQJsNQqmhXgRi8uhvWWuPYZCNlzT8qAyblUgNoXdHdjMTzAqeGjAoli8f+bzPA== + dependencies: + has-property-descriptors "^1.0.0" + object-keys "^1.1.1" + +delayed-stream@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" + integrity sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ== + +diff@5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/diff/-/diff-5.0.0.tgz#7ed6ad76d859d030787ec35855f5b1daf31d852b" + integrity sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w== + +diff@^4.0.1: + version "4.0.2" + resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d" + integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A== + +ed2curve@^0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/ed2curve/-/ed2curve-0.3.0.tgz#322b575152a45305429d546b071823a93129a05d" + integrity sha512-8w2fmmq3hv9rCrcI7g9hms2pMunQr1JINfcjwR9tAyZqhtyaMN991lF/ZfHfr5tzZQ8c7y7aBgZbjfbd0fjFwQ== + dependencies: + tweetnacl "1.x.x" + +emoji-regex@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" + integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== + +es-abstract@^1.19.0, es-abstract@^1.19.1, es-abstract@^1.19.5: + version "1.20.3" + resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.20.3.tgz#90b143ff7aedc8b3d189bcfac7f1e3e3f81e9da1" + integrity sha512-AyrnaKVpMzljIdwjzrj+LxGmj8ik2LckwXacHqrJJ/jxz6dDDBcZ7I7nlHM0FvEW8MfbWJwOd+yT2XzYW49Frw== + dependencies: + call-bind "^1.0.2" + es-to-primitive "^1.2.1" + function-bind "^1.1.1" + function.prototype.name "^1.1.5" + get-intrinsic "^1.1.3" + get-symbol-description "^1.0.0" + has "^1.0.3" + has-property-descriptors "^1.0.0" + has-symbols "^1.0.3" + internal-slot "^1.0.3" + is-callable "^1.2.6" + is-negative-zero "^2.0.2" + is-regex "^1.1.4" + is-shared-array-buffer "^1.0.2" + is-string "^1.0.7" + is-weakref "^1.0.2" + object-inspect "^1.12.2" + object-keys "^1.1.1" + object.assign "^4.1.4" + regexp.prototype.flags "^1.4.3" + safe-regex-test "^1.0.0" + string.prototype.trimend "^1.0.5" + string.prototype.trimstart "^1.0.5" + unbox-primitive "^1.0.2" + +es-to-primitive@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/es-to-primitive/-/es-to-primitive-1.2.1.tgz#e55cd4c9cdc188bcefb03b366c736323fc5c898a" + integrity sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA== + dependencies: + is-callable "^1.1.4" + is-date-object "^1.0.1" + is-symbol "^1.0.2" + +es5-ext@^0.10.35, es5-ext@^0.10.50: + version "0.10.62" + resolved "https://registry.yarnpkg.com/es5-ext/-/es5-ext-0.10.62.tgz#5e6adc19a6da524bf3d1e02bbc8960e5eb49a9a5" + integrity sha512-BHLqn0klhEpnOKSrzn/Xsz2UIW8j+cGmo9JLzr8BiUapV8hPL9+FliFqjwr9ngW7jWdnxv6eO+/LqyhJVqgrjA== + dependencies: + es6-iterator "^2.0.3" + es6-symbol "^3.1.3" + next-tick "^1.1.0" + +es6-iterator@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/es6-iterator/-/es6-iterator-2.0.3.tgz#a7de889141a05a94b0854403b2d0a0fbfa98f3b7" + integrity sha512-zw4SRzoUkd+cl+ZoE15A9o1oQd920Bb0iOJMQkQhl3jNc03YqVjAhG7scf9C5KWRU/R13Orf588uCC6525o02g== + dependencies: + d "1" + es5-ext "^0.10.35" + es6-symbol "^3.1.1" + +es6-symbol@^3.1.1, es6-symbol@^3.1.3: + version "3.1.3" + resolved "https://registry.yarnpkg.com/es6-symbol/-/es6-symbol-3.1.3.tgz#bad5d3c1bcdac28269f4cb331e431c78ac705d18" + integrity sha512-NJ6Yn3FuDinBaBRWl/q5X/s4koRHBrgKAu+yGI6JCBeiu3qrcbJhwT2GeR/EXVfylRk8dpQVJoLEFhK+Mu31NA== + dependencies: + d "^1.0.1" + ext "^1.1.2" + +escalade@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40" + integrity sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw== + +escape-string-regexp@4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34" + integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== + +eventemitter3@^4.0.7: + version "4.0.7" + resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.7.tgz#2de9b68f6528d5644ef5c59526a1b4a07306169f" + integrity sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw== + +ext@^1.1.2: + version "1.7.0" + resolved "https://registry.yarnpkg.com/ext/-/ext-1.7.0.tgz#0ea4383c0103d60e70be99e9a7f11027a33c4f5f" + integrity sha512-6hxeJYaL110a9b5TEJSj0gojyHQAmA2ch5Os+ySCiA1QGdS697XWY1pzsrSjqA9LDEEgdB/KypIlR59RcLuHYw== + dependencies: + type "^2.7.2" + +fetch-blob@^3.1.2, fetch-blob@^3.1.4: + version "3.2.0" + resolved "https://registry.yarnpkg.com/fetch-blob/-/fetch-blob-3.2.0.tgz#f09b8d4bbd45adc6f0c20b7e787e793e309dcce9" + integrity sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ== + dependencies: + node-domexception "^1.0.0" + web-streams-polyfill "^3.0.3" + +fill-range@^7.0.1: + version "7.0.1" + resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40" + integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ== + dependencies: + to-regex-range "^5.0.1" + +find-up@5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-5.0.0.tgz#4c92819ecb7083561e4f4a240a86be5198f536fc" + integrity sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng== + dependencies: + locate-path "^6.0.0" + path-exists "^4.0.0" + +flat@^5.0.2: + version "5.0.2" + resolved "https://registry.yarnpkg.com/flat/-/flat-5.0.2.tgz#8ca6fe332069ffa9d324c327198c598259ceb241" + integrity sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ== + +form-data@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/form-data/-/form-data-3.0.1.tgz#ebd53791b78356a99af9a300d4282c4d5eb9755f" + integrity sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg== + dependencies: + asynckit "^0.4.0" + combined-stream "^1.0.8" + mime-types "^2.1.12" + +formdata-polyfill@^4.0.10: + version "4.0.10" + resolved "https://registry.yarnpkg.com/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz#24807c31c9d402e002ab3d8c720144ceb8848423" + integrity sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g== + dependencies: + fetch-blob "^3.1.2" + +fs.realpath@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" + integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw== + +fsevents@~2.3.2: + version "2.3.2" + resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a" + integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA== + +function-bind@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" + integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A== + +function.prototype.name@^1.1.5: + version "1.1.5" + resolved "https://registry.yarnpkg.com/function.prototype.name/-/function.prototype.name-1.1.5.tgz#cce0505fe1ffb80503e6f9e46cc64e46a12a9621" + integrity sha512-uN7m/BzVKQnCUF/iW8jYea67v++2u7m5UgENbHRtdDVclOUP+FMPlCNdmk0h/ysGyo2tavMJEDqJAkJdRa1vMA== + dependencies: + call-bind "^1.0.2" + define-properties "^1.1.3" + es-abstract "^1.19.0" + functions-have-names "^1.2.2" + +functions-have-names@^1.2.2: + version "1.2.3" + resolved "https://registry.yarnpkg.com/functions-have-names/-/functions-have-names-1.2.3.tgz#0404fe4ee2ba2f607f0e0ec3c80bae994133b834" + integrity sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ== + +get-caller-file@^2.0.5: + version "2.0.5" + resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" + integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== + +get-func-name@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/get-func-name/-/get-func-name-2.0.0.tgz#ead774abee72e20409433a066366023dd6887a41" + integrity sha512-Hm0ixYtaSZ/V7C8FJrtZIuBBI+iSgL+1Aq82zSu8VQNB4S3Gk8e7Qs3VwBDJAhmRZcFqkl3tQu36g/Foh5I5ig== + +get-intrinsic@^1.0.2, get-intrinsic@^1.1.0, get-intrinsic@^1.1.1, get-intrinsic@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.1.3.tgz#063c84329ad93e83893c7f4f243ef63ffa351385" + integrity sha512-QJVz1Tj7MS099PevUG5jvnt9tSkXN8K14dxQlikJuPt4uD9hHAHjLyLBiLR5zELelBdD9QNRAXZzsJx0WaDL9A== + dependencies: + function-bind "^1.1.1" + has "^1.0.3" + has-symbols "^1.0.3" + +get-symbol-description@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/get-symbol-description/-/get-symbol-description-1.0.0.tgz#7fdb81c900101fbd564dd5f1a30af5aadc1e58d6" + integrity sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw== + dependencies: + call-bind "^1.0.2" + get-intrinsic "^1.1.1" + +glob-parent@~5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" + integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== + dependencies: + is-glob "^4.0.1" + +glob@7.2.0: + version "7.2.0" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.0.tgz#d15535af7732e02e948f4c41628bd910293f6023" + integrity sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.0.4" + once "^1.3.0" + path-is-absolute "^1.0.0" + +has-bigints@^1.0.1, has-bigints@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/has-bigints/-/has-bigints-1.0.2.tgz#0871bd3e3d51626f6ca0966668ba35d5602d6eaa" + integrity sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ== + +has-flag@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" + integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== + +has-property-descriptors@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz#610708600606d36961ed04c196193b6a607fa861" + integrity sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ== + dependencies: + get-intrinsic "^1.1.1" + +has-symbols@^1.0.2, has-symbols@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.3.tgz#bb7b2c4349251dce87b125f7bdf874aa7c8b39f8" + integrity sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A== + +has-tostringtag@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/has-tostringtag/-/has-tostringtag-1.0.0.tgz#7e133818a7d394734f941e73c3d3f9291e658b25" + integrity sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ== + dependencies: + has-symbols "^1.0.2" + +has@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796" + integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw== + dependencies: + function-bind "^1.1.1" + +he@1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f" + integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw== + +inflight@^1.0.4: + version "1.0.6" + resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" + integrity sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA== + dependencies: + once "^1.3.0" + wrappy "1" + +inherits@2: + version "2.0.4" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" + integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== + +internal-slot@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/internal-slot/-/internal-slot-1.0.3.tgz#7347e307deeea2faac2ac6205d4bc7d34967f59c" + integrity sha512-O0DB1JC/sPyZl7cIo78n5dR7eUSwwpYPiXRhTzNxZVAMUuB8vlnRFyLxdrVToks6XPLVnFfbzaVd5WLjhgg+vA== + dependencies: + get-intrinsic "^1.1.0" + has "^1.0.3" + side-channel "^1.0.4" + +is-bigint@^1.0.1: + version "1.0.4" + resolved "https://registry.yarnpkg.com/is-bigint/-/is-bigint-1.0.4.tgz#08147a1875bc2b32005d41ccd8291dffc6691df3" + integrity sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg== + dependencies: + has-bigints "^1.0.1" + +is-binary-path@~2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09" + integrity sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw== + dependencies: + binary-extensions "^2.0.0" + +is-boolean-object@^1.1.0: + version "1.1.2" + resolved "https://registry.yarnpkg.com/is-boolean-object/-/is-boolean-object-1.1.2.tgz#5c6dc200246dd9321ae4b885a114bb1f75f63719" + integrity sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA== + dependencies: + call-bind "^1.0.2" + has-tostringtag "^1.0.0" + +is-callable@^1.1.4, is-callable@^1.2.6: + version "1.2.7" + resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.7.tgz#3bc2a85ea742d9e36205dcacdd72ca1fdc51b055" + integrity sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA== + +is-date-object@^1.0.1: + version "1.0.5" + resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.0.5.tgz#0841d5536e724c25597bf6ea62e1bd38298df31f" + integrity sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ== + dependencies: + has-tostringtag "^1.0.0" + +is-extglob@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" + integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ== + +is-fullwidth-code-point@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" + integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== + +is-glob@^4.0.1, is-glob@~4.0.1: + version "4.0.3" + resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084" + integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== + dependencies: + is-extglob "^2.1.1" + +is-negative-zero@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/is-negative-zero/-/is-negative-zero-2.0.2.tgz#7bf6f03a28003b8b3965de3ac26f664d765f3150" + integrity sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA== + +is-number-object@^1.0.4: + version "1.0.7" + resolved "https://registry.yarnpkg.com/is-number-object/-/is-number-object-1.0.7.tgz#59d50ada4c45251784e9904f5246c742f07a42fc" + integrity sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ== + dependencies: + has-tostringtag "^1.0.0" + +is-number@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" + integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== + +is-plain-obj@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-2.1.0.tgz#45e42e37fccf1f40da8e5f76ee21515840c09287" + integrity sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA== + +is-regex@^1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.1.4.tgz#eef5663cd59fa4c0ae339505323df6854bb15958" + integrity sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg== + dependencies: + call-bind "^1.0.2" + has-tostringtag "^1.0.0" + +is-shared-array-buffer@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz#8f259c573b60b6a32d4058a1a07430c0a7344c79" + integrity sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA== + dependencies: + call-bind "^1.0.2" + +is-string@^1.0.5, is-string@^1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/is-string/-/is-string-1.0.7.tgz#0dd12bf2006f255bb58f695110eff7491eebc0fd" + integrity sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg== + dependencies: + has-tostringtag "^1.0.0" + +is-symbol@^1.0.2, is-symbol@^1.0.3: + version "1.0.4" + resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.0.4.tgz#a6dac93b635b063ca6872236de88910a57af139c" + integrity sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg== + dependencies: + has-symbols "^1.0.2" + +is-typedarray@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a" + integrity sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA== + +is-unicode-supported@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz#3f26c76a809593b52bfa2ecb5710ed2779b522a7" + integrity sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw== + +is-weakref@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/is-weakref/-/is-weakref-1.0.2.tgz#9529f383a9338205e89765e0392efc2f100f06f2" + integrity sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ== + dependencies: + call-bind "^1.0.2" + +js-yaml@4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602" + integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA== + dependencies: + argparse "^2.0.1" + +json-stringify-safe@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb" + integrity sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA== + +locate-path@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-6.0.0.tgz#55321eb309febbc59c4801d931a72452a681d286" + integrity sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw== + dependencies: + p-locate "^5.0.0" + +lodash@^4.17.21: + version "4.17.21" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" + integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== + +log-symbols@4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-4.1.0.tgz#3fbdbb95b4683ac9fc785111e792e558d4abd503" + integrity sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg== + dependencies: + chalk "^4.1.0" + is-unicode-supported "^0.1.0" + +loupe@^2.3.1: + version "2.3.4" + resolved "https://registry.yarnpkg.com/loupe/-/loupe-2.3.4.tgz#7e0b9bffc76f148f9be769cb1321d3dcf3cb25f3" + integrity sha512-OvKfgCC2Ndby6aSTREl5aCCPTNIzlDfQZvZxNUrBrihDhL3xcrYegTblhmEiCrg2kKQz4XsFIaemE5BF4ybSaQ== + dependencies: + get-func-name "^2.0.0" + +make-error@^1.1.1: + version "1.3.6" + resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.6.tgz#2eb2e37ea9b67c4891f684a1394799af484cf7a2" + integrity sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw== + +micro-base58@^0.5.1: + version "0.5.1" + resolved "https://registry.yarnpkg.com/micro-base58/-/micro-base58-0.5.1.tgz#008892e621fd0d38caab3288a9dd0e65cb4b6200" + integrity sha512-iwqAmg66VjB2LA3BcUxyrOyqck4HLLtSzKnx2VQSnN5piQji598N15P8RRx2d6lPvJ98B1b0cl2VbvQeFeWdig== + +mime-db@1.52.0: + version "1.52.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70" + integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== + +mime-types@^2.1.12: + version "2.1.35" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a" + integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== + dependencies: + mime-db "1.52.0" + +minimatch@5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-5.0.1.tgz#fb9022f7528125187c92bd9e9b6366be1cf3415b" + integrity sha512-nLDxIFRyhDblz3qMuq+SoRZED4+miJ/G+tdDrjkkkRnjAsBexeGpgjLEQ0blJy7rHhR2b93rhQY4SvyWu9v03g== + dependencies: + brace-expansion "^2.0.1" + +minimatch@^3.0.4: + version "3.1.2" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" + integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== + dependencies: + brace-expansion "^1.1.7" + +mocha-steps@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/mocha-steps/-/mocha-steps-1.3.0.tgz#2449231ec45ec56810f65502cb22e2571862957f" + integrity sha512-KZvpMJTqzLZw3mOb+EEuYi4YZS41C9iTnb7skVFRxHjUd1OYbl64tCMSmpdIRM9LnwIrSOaRfPtNpF5msgv6Eg== + +mocha@^10.1.0: + version "10.1.0" + resolved "https://registry.yarnpkg.com/mocha/-/mocha-10.1.0.tgz#dbf1114b7c3f9d0ca5de3133906aea3dfc89ef7a" + integrity sha512-vUF7IYxEoN7XhQpFLxQAEMtE4W91acW4B6En9l97MwE9stL1A9gusXfoHZCLVHDUJ/7V5+lbCM6yMqzo5vNymg== + dependencies: + ansi-colors "4.1.1" + browser-stdout "1.3.1" + chokidar "3.5.3" + debug "4.3.4" + diff "5.0.0" + escape-string-regexp "4.0.0" + find-up "5.0.0" + glob "7.2.0" + he "1.2.0" + js-yaml "4.1.0" + log-symbols "4.1.0" + minimatch "5.0.1" + ms "2.1.3" + nanoid "3.3.3" + serialize-javascript "6.0.0" + strip-json-comments "3.1.1" + supports-color "8.1.1" + workerpool "6.2.1" + yargs "16.2.0" + yargs-parser "20.2.4" + yargs-unparser "2.0.0" + +mock-socket@^9.1.5: + version "9.1.5" + resolved "https://registry.yarnpkg.com/mock-socket/-/mock-socket-9.1.5.tgz#2c4e44922ad556843b6dfe09d14ed8041fa2cdeb" + integrity sha512-3DeNIcsQixWHHKk6NdoBhWI4t1VMj5/HzfnI1rE/pLl5qKx7+gd4DNA07ehTaZ6MoUU053si6Hd+YtiM/tQZfg== + +ms@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" + integrity sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A== + +ms@2.1.2: + version "2.1.2" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" + integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== + +ms@2.1.3: + version "2.1.3" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" + integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== + +nanoid@3.3.3: + version "3.3.3" + resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.3.tgz#fd8e8b7aa761fe807dba2d1b98fb7241bb724a25" + integrity sha512-p1sjXuopFs0xg+fPASzQ28agW1oHD7xDsd9Xkf3T15H3c/cifrFHVwrh74PdoklAPi+i7MdRsE47vm2r6JoB+w== + +next-tick@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/next-tick/-/next-tick-1.1.0.tgz#1836ee30ad56d67ef281b22bd199f709449b35eb" + integrity sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ== + +nock@^13.2.9: + version "13.2.9" + resolved "https://registry.yarnpkg.com/nock/-/nock-13.2.9.tgz#4faf6c28175d36044da4cfa68e33e5a15086ad4c" + integrity sha512-1+XfJNYF1cjGB+TKMWi29eZ0b82QOvQs2YoLNzbpWGqFMtRQHTa57osqdGj4FrFPgkO4D4AZinzUJR9VvW3QUA== + dependencies: + debug "^4.1.0" + json-stringify-safe "^5.0.1" + lodash "^4.17.21" + propagate "^2.0.0" + +node-domexception@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/node-domexception/-/node-domexception-1.0.0.tgz#6888db46a1f71c0b76b3f7555016b63fe64766e5" + integrity sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ== + +node-fetch@^3.2.10: + version "3.2.10" + resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-3.2.10.tgz#e8347f94b54ae18b57c9c049ef641cef398a85c8" + integrity sha512-MhuzNwdURnZ1Cp4XTazr69K0BTizsBroX7Zx3UgDSVcZYKF/6p0CBe4EUb/hLqmzVhl0UpYfgRljQ4yxE+iCxA== + dependencies: + data-uri-to-buffer "^4.0.0" + fetch-blob "^3.1.4" + formdata-polyfill "^4.0.10" + +node-gyp-build@^4.3.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/node-gyp-build/-/node-gyp-build-4.5.0.tgz#7a64eefa0b21112f89f58379da128ac177f20e40" + integrity sha512-2iGbaQBV+ITgCz76ZEjmhUKAKVf7xfY1sRl4UiKQspfZMH2h06SyhNsnSVy50cwkFQDGLyif6m/6uFXHkOZ6rg== + +normalize-path@^3.0.0, normalize-path@~3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" + integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== + +object-inspect@^1.12.2, object-inspect@^1.9.0: + version "1.12.2" + resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.12.2.tgz#c0641f26394532f28ab8d796ab954e43c009a8ea" + integrity sha512-z+cPxW0QGUp0mcqcsgQyLVRDoXFQbXOwBaqyF7VIgI4TWNQsDHrBpUQslRmIfAoYWdYzs6UlKJtB2XJpTaNSpQ== + +object-keys@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e" + integrity sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA== + +object.assign@^4.1.4: + version "4.1.4" + resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.4.tgz#9673c7c7c351ab8c4d0b516f4343ebf4dfb7799f" + integrity sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ== + dependencies: + call-bind "^1.0.2" + define-properties "^1.1.4" + has-symbols "^1.0.3" + object-keys "^1.1.1" + +once@^1.3.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" + integrity sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w== + dependencies: + wrappy "1" + +p-limit@^3.0.2: + version "3.1.0" + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-3.1.0.tgz#e1daccbe78d0d1388ca18c64fea38e3e57e3706b" + integrity sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ== + dependencies: + yocto-queue "^0.1.0" + +p-locate@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-5.0.0.tgz#83c8315c6785005e3bd021839411c9e110e6d834" + integrity sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw== + dependencies: + p-limit "^3.0.2" + +pako@^2.0.4: + version "2.0.4" + resolved "https://registry.yarnpkg.com/pako/-/pako-2.0.4.tgz#6cebc4bbb0b6c73b0d5b8d7e8476e2b2fbea576d" + integrity sha512-v8tweI900AUkZN6heMU/4Uy4cXRc2AYNRggVmTR+dEncawDJgCdLMximOVA2p4qO57WMynangsfGRb5WD6L1Bg== + +path-exists@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3" + integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w== + +path-is-absolute@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" + integrity sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg== + +pathval@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/pathval/-/pathval-1.1.1.tgz#8534e77a77ce7ac5a2512ea21e0fdb8fcf6c3d8d" + integrity sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ== + +picomatch@^2.0.4, picomatch@^2.2.1: + version "2.3.1" + resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" + integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== + +promise-controller@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/promise-controller/-/promise-controller-1.0.0.tgz#81ebea71271aa40ac8f3bebccab3d4158dc4cc02" + integrity sha512-goA0zA9L91tuQbUmiMinSYqlyUtEgg4fxJcjYnLYOQnrktb4o4UqciXDNXiRUPiDBPACmsr1k8jDW4r7UDq9Qw== + +promise.prototype.finally@^3.1.2: + version "3.1.3" + resolved "https://registry.yarnpkg.com/promise.prototype.finally/-/promise.prototype.finally-3.1.3.tgz#d3186e58fcf4df1682a150f934ccc27b7893389c" + integrity sha512-EXRF3fC9/0gz4qkt/f5EP5iW4kj9oFpBICNpCNOb/52+8nlHIX07FPLbi/q4qYBQ1xZqivMzTpNQSnArVASolQ== + dependencies: + call-bind "^1.0.2" + define-properties "^1.1.3" + es-abstract "^1.19.1" + +promised-map@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/promised-map/-/promised-map-1.0.0.tgz#aa3ec59e13724d37b946415f6850273af8e213b5" + integrity sha512-fP9VSMgcml+U2uJ9PBc4/LDQ3ZkJCH4blLNCS6gbH7RHyRZCYs91zxWHqiUy+heFiEMiB2op/qllYoFqmIqdWA== + +propagate@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/propagate/-/propagate-2.0.1.tgz#40cdedab18085c792334e64f0ac17256d38f9a45" + integrity sha512-vGrhOavPSTz4QVNuBNdcNXePNdNMaO1xj9yBeH1ScQPjk/rhg9sSlCXPhMkFuaNNW/syTvYqsnbIJxMBfRbbag== + +randombytes@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.1.0.tgz#df6f84372f0270dc65cdf6291349ab7a473d4f2a" + integrity sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ== + dependencies: + safe-buffer "^5.1.0" + +readdirp@~3.6.0: + version "3.6.0" + resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.6.0.tgz#74a370bd857116e245b29cc97340cd431a02a6c7" + integrity sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA== + dependencies: + picomatch "^2.2.1" + +regenerator-runtime@^0.13.4: + version "0.13.9" + resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz#8925742a98ffd90814988d7566ad30ca3b263b52" + integrity sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA== + +regexp.prototype.flags@^1.4.3: + version "1.4.3" + resolved "https://registry.yarnpkg.com/regexp.prototype.flags/-/regexp.prototype.flags-1.4.3.tgz#87cab30f80f66660181a3bb7bf5981a872b367ac" + integrity sha512-fjggEOO3slI6Wvgjwflkc4NFRCTZAu5CnNfBd5qOMYhWdn67nJBBu34/TkD++eeFmd8C9r9jfXJ27+nSiRkSUA== + dependencies: + call-bind "^1.0.2" + define-properties "^1.1.3" + functions-have-names "^1.2.2" + +require-directory@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" + integrity sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q== + +rxjs@^7.5.7: + version "7.5.7" + resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-7.5.7.tgz#2ec0d57fdc89ece220d2e702730ae8f1e49def39" + integrity sha512-z9MzKh/UcOqB3i20H6rtrlaE/CgjLOvheWK/9ILrbhROGTweAi1BaFsTT9FbwZi5Trr1qNRs+MXkhmR06awzQA== + dependencies: + tslib "^2.1.0" + +safe-buffer@^5.1.0: + version "5.2.1" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" + integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== + +safe-regex-test@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/safe-regex-test/-/safe-regex-test-1.0.0.tgz#793b874d524eb3640d1873aad03596db2d4f2295" + integrity sha512-JBUUzyOgEwXQY1NuPtvcj/qcBDbDmEvWufhlnXZIm75DEHp+afM1r1ujJpJsV/gSM4t59tpDyPi1sd6ZaPFfsA== + dependencies: + call-bind "^1.0.2" + get-intrinsic "^1.1.3" + is-regex "^1.1.4" + +serialize-javascript@6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-6.0.0.tgz#efae5d88f45d7924141da8b5c3a7a7e663fefeb8" + integrity sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag== + dependencies: + randombytes "^2.1.0" + +side-channel@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.4.tgz#efce5c8fdc104ee751b25c58d4290011fa5ea2cf" + integrity sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw== + dependencies: + call-bind "^1.0.0" + get-intrinsic "^1.0.2" + object-inspect "^1.9.0" + +string-width@^4.1.0, string-width@^4.2.0: + version "4.2.3" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" + integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== + dependencies: + emoji-regex "^8.0.0" + is-fullwidth-code-point "^3.0.0" + strip-ansi "^6.0.1" + +string.prototype.trimend@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.5.tgz#914a65baaab25fbdd4ee291ca7dde57e869cb8d0" + integrity sha512-I7RGvmjV4pJ7O3kdf+LXFpVfdNOxtCW/2C8f6jNiW4+PQchwxkCDzlk1/7p+Wl4bqFIZeF47qAHXLuHHWKAxog== + dependencies: + call-bind "^1.0.2" + define-properties "^1.1.4" + es-abstract "^1.19.5" + +string.prototype.trimstart@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/string.prototype.trimstart/-/string.prototype.trimstart-1.0.5.tgz#5466d93ba58cfa2134839f81d7f42437e8c01fef" + integrity sha512-THx16TJCGlsN0o6dl2o6ncWUsdgnLRSA23rRE5pyGBw/mLr3Ej/R2LaqCtgP8VNMGZsvMWnf9ooZPyY2bHvUFg== + dependencies: + call-bind "^1.0.2" + define-properties "^1.1.4" + es-abstract "^1.19.5" + +strip-ansi@^6.0.0, strip-ansi@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" + integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== + dependencies: + ansi-regex "^5.0.1" + +strip-json-comments@3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" + integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== + +supports-color@8.1.1: + version "8.1.1" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-8.1.1.tgz#cd6fc17e28500cff56c1b86c0a7fd4a54a73005c" + integrity sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q== + dependencies: + has-flag "^4.0.0" + +supports-color@^7.1.0: + version "7.2.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" + integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== + dependencies: + has-flag "^4.0.0" + +to-regex-range@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" + integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ== + dependencies: + is-number "^7.0.0" + +ts-node@^10.8.1: + version "10.9.1" + resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-10.9.1.tgz#e73de9102958af9e1f0b168a6ff320e25adcff4b" + integrity sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw== + dependencies: + "@cspotcode/source-map-support" "^0.8.0" + "@tsconfig/node10" "^1.0.7" + "@tsconfig/node12" "^1.0.7" + "@tsconfig/node14" "^1.0.0" + "@tsconfig/node16" "^1.0.2" + acorn "^8.4.1" + acorn-walk "^8.1.1" + arg "^4.1.0" + create-require "^1.1.0" + diff "^4.0.1" + make-error "^1.1.1" + v8-compile-cache-lib "^3.0.1" + yn "3.1.1" + +tslib@^2.1.0: + version "2.4.0" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.4.0.tgz#7cecaa7f073ce680a05847aa77be941098f36dc3" + integrity sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ== + +tweetnacl@1.x.x, tweetnacl@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-1.0.3.tgz#ac0af71680458d8a6378d0d0d050ab1407d35596" + integrity sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw== + +type-detect@^4.0.0, type-detect@^4.0.5: + version "4.0.8" + resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.8.tgz#7646fb5f18871cfbb7749e69bd39a6388eb7450c" + integrity sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g== + +type@^1.0.1: + version "1.2.0" + resolved "https://registry.yarnpkg.com/type/-/type-1.2.0.tgz#848dd7698dafa3e54a6c479e759c4bc3f18847a0" + integrity sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg== + +type@^2.7.2: + version "2.7.2" + resolved "https://registry.yarnpkg.com/type/-/type-2.7.2.tgz#2376a15a3a28b1efa0f5350dcf72d24df6ef98d0" + integrity sha512-dzlvlNlt6AXU7EBSfpAscydQ7gXB+pPGsPnfJnZpiNJBDj7IaJzQlBZYGdEi4R9HmPdBv2XmWJ6YUtoTa7lmCw== + +typedarray-to-buffer@^3.1.5: + version "3.1.5" + resolved "https://registry.yarnpkg.com/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz#a97ee7a9ff42691b9f783ff1bc5112fe3fca9080" + integrity sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q== + dependencies: + is-typedarray "^1.0.0" + +typescript@^4.7.3: + version "4.8.4" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.8.4.tgz#c464abca159669597be5f96b8943500b238e60e6" + integrity sha512-QCh+85mCy+h0IGff8r5XWzOVSbBO+KfeYrMQh7NJ58QujwcE22u+NUSmUxqF+un70P9GXKxa2HCNiTTMJknyjQ== + +unbox-primitive@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/unbox-primitive/-/unbox-primitive-1.0.2.tgz#29032021057d5e6cdbd08c5129c226dff8ed6f9e" + integrity sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw== + dependencies: + call-bind "^1.0.2" + has-bigints "^1.0.2" + has-symbols "^1.0.3" + which-boxed-primitive "^1.0.2" + +utf-8-validate@^5.0.2: + version "5.0.9" + resolved "https://registry.yarnpkg.com/utf-8-validate/-/utf-8-validate-5.0.9.tgz#ba16a822fbeedff1a58918f2a6a6b36387493ea3" + integrity sha512-Yek7dAy0v3Kl0orwMlvi7TPtiCNrdfHNd7Gcc/pLq4BLXqfAmd0J7OWMizUQnTTJsyjKn02mU7anqwfmUP4J8Q== + dependencies: + node-gyp-build "^4.3.0" + +v8-compile-cache-lib@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz#6336e8d71965cb3d35a1bbb7868445a7c05264bf" + integrity sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg== + +web-streams-polyfill@^3.0.3: + version "3.2.1" + resolved "https://registry.yarnpkg.com/web-streams-polyfill/-/web-streams-polyfill-3.2.1.tgz#71c2718c52b45fd49dbeee88634b3a60ceab42a6" + integrity sha512-e0MO3wdXWKrLbL0DgGnUV7WHVuw9OUvL4hjgnPkIeEvESk74gAITi5G606JtZPp39cd8HA9VQzCIvA49LpPN5Q== + +websocket-as-promised@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/websocket-as-promised/-/websocket-as-promised-2.0.1.tgz#d18b7c160bba294585aaf6fa9572f31e05cfb175" + integrity sha512-ePV26D/D37ughXU9j+DjGmwUbelWJrC/vi+6GK++fRlBJmS7aU9T8ABu47KFF0O7r6XN2NAuqJRpegbUwXZxQg== + dependencies: + chnl "^1.2.0" + promise-controller "^1.0.0" + promise.prototype.finally "^3.1.2" + promised-map "^1.0.0" + +websocket@^1.0.34: + version "1.0.34" + resolved "https://registry.yarnpkg.com/websocket/-/websocket-1.0.34.tgz#2bdc2602c08bf2c82253b730655c0ef7dcab3111" + integrity sha512-PRDso2sGwF6kM75QykIesBijKSVceR6jL2G8NGYyq2XrItNC2P5/qL5XeR056GhA+Ly7JMFvJb9I312mJfmqnQ== + dependencies: + bufferutil "^4.0.1" + debug "^2.2.0" + es5-ext "^0.10.50" + typedarray-to-buffer "^3.1.5" + utf-8-validate "^5.0.2" + yaeti "^0.0.6" + +which-boxed-primitive@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz#13757bc89b209b049fe5d86430e21cf40a89a8e6" + integrity sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg== + dependencies: + is-bigint "^1.0.1" + is-boolean-object "^1.1.0" + is-number-object "^1.0.4" + is-string "^1.0.5" + is-symbol "^1.0.3" + +workerpool@6.2.1: + version "6.2.1" + resolved "https://registry.yarnpkg.com/workerpool/-/workerpool-6.2.1.tgz#46fc150c17d826b86a008e5a4508656777e9c343" + integrity sha512-ILEIE97kDZvF9Wb9f6h5aXK4swSlKGUcOEGiIYb2OOu/IrDU9iwj0fD//SsA6E5ibwJxpEvhullJY4Sl4GcpAw== + +wrap-ansi@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" + integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + +wrappy@1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" + integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ== + +ws@^8.8.1: + version "8.9.0" + resolved "https://registry.yarnpkg.com/ws/-/ws-8.9.0.tgz#2a994bb67144be1b53fe2d23c53c028adeb7f45e" + integrity sha512-Ja7nszREasGaYUYCI2k4lCKIRTt+y7XuqVoHR44YpI49TtryyqbqvDMn5eqfW7e6HzTukDRIsXqzVHScqRcafg== + +y18n@^5.0.5: + version "5.0.8" + resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55" + integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA== + +yaeti@^0.0.6: + version "0.0.6" + resolved "https://registry.yarnpkg.com/yaeti/-/yaeti-0.0.6.tgz#f26f484d72684cf42bedfb76970aa1608fbf9577" + integrity sha512-MvQa//+KcZCUkBTIC9blM+CU9J2GzuTytsOUwf2lidtvkx/6gnEp1QvJv34t9vdjhFmha/mUiNDbN0D0mJWdug== + +yargs-parser@20.2.4: + version "20.2.4" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.4.tgz#b42890f14566796f85ae8e3a25290d205f154a54" + integrity sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA== + +yargs-parser@^20.2.2: + version "20.2.9" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.9.tgz#2eb7dc3b0289718fc295f362753845c41a0c94ee" + integrity sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w== + +yargs-unparser@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/yargs-unparser/-/yargs-unparser-2.0.0.tgz#f131f9226911ae5d9ad38c432fe809366c2325eb" + integrity sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA== + dependencies: + camelcase "^6.0.0" + decamelize "^4.0.0" + flat "^5.0.2" + is-plain-obj "^2.1.0" + +yargs@16.2.0: + version "16.2.0" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-16.2.0.tgz#1c82bf0f6b6a66eafce7ef30e376f49a12477f66" + integrity sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw== + dependencies: + cliui "^7.0.2" + escalade "^3.1.1" + get-caller-file "^2.0.5" + require-directory "^2.1.1" + string-width "^4.2.0" + y18n "^5.0.5" + yargs-parser "^20.2.2" + +yn@3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/yn/-/yn-3.1.1.tgz#1e87401a09d767c1d5eab26a6e4c185182d2eb50" + integrity sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q== + +yocto-queue@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" + integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== diff --git a/tee-worker/upstream_commit b/tee-worker/upstream_commit new file mode 100644 index 0000000000..0ed8e313ed --- /dev/null +++ b/tee-worker/upstream_commit @@ -0,0 +1 @@ +decd3afd