From 0dfd4c6ca29455583648f0067640e25da4136b15 Mon Sep 17 00:00:00 2001 From: Fedor Ivanov Date: Wed, 6 Nov 2024 14:54:43 +0300 Subject: [PATCH] feat: zilliqa consensus data related to block (#10699) * refactor: DRY declaration of chain type and feature dependent repos * feat: add `:zilliqa` chain type * chore: add `zilliqa` to cspell.json * feat: import consensus data related to block * fix: cspell * fix: ethereum_jsonrpc tests * fix: format & credo * feat: render consensus data in `/api/v2/blocks/:block_hash_or_number` response * fix: add missing `view` field to block db schema * feat: add workflow to publish docker image * fix: cast `zilliqa_view` field of a block * refactor: view * refactor: define spec only once * refactor: DRY definition of other repos * fix: remove not null constraint for `zilliqa_view` * fix: dialyzer * fix: `@spec` for `chain_type_fields/2` * feat: github workflows * fix: debug * fix: add missing indexer and API workflows --- .github/workflows/config.yml | 19 +- .github/workflows/pre-release-zilliqa.yml | 167 ++++++++++++++ .../publish-docker-image-for-zilliqa.yml | 162 ++++++++++++++ .github/workflows/release-zilliqa.yml | 164 ++++++++++++++ .../controllers/api/v2/block_controller.ex | 8 + .../views/api/v2/block_view.ex | 6 + .../views/api/v2/zilliqa_view.ex | 88 ++++++++ .../lib/ethereum_jsonrpc/block.ex | 111 ++++++++-- .../lib/ethereum_jsonrpc/blocks.ex | 61 ++++- .../lib/ethereum_jsonrpc/zilliqa.ex | 20 ++ .../zilliqa/aggregate_quorum_certificate.ex | 186 ++++++++++++++++ .../lib/ethereum_jsonrpc/zilliqa/helper.ex | 111 ++++++++++ .../zilliqa/nested_quorum_certificates.ex | 187 ++++++++++++++++ .../zilliqa/quorum_certificate.ex | 100 +++++++++ apps/ethereum_jsonrpc/mix.exs | 2 +- .../test/ethereum_jsonrpc/block_test.exs | 4 + .../test/ethereum_jsonrpc/zilliqa_test.exs | 7 + apps/explorer/config/dev.exs | 74 +++---- apps/explorer/config/prod.exs | 128 +++-------- apps/explorer/config/test.exs | 21 +- apps/explorer/lib/explorer/application.ex | 19 +- apps/explorer/lib/explorer/chain/block.ex | 23 ++ .../zilliqa/aggregate_quorum_certificates.ex | 79 +++++++ .../zilliqa/nested_quorum_certificates.ex | 86 +++++++ .../runner/zilliqa/quorum_certificates.ex | 79 +++++++ .../chain/import/stage/chain_type_specific.ex | 5 + .../zilliqa/aggregate_quorum_certificate.ex | 64 ++++++ .../explorer/chain/zilliqa/hash/signature.ex | 60 +++++ .../zilliqa/nested_quorum_certificate.ex | 64 ++++++ .../chain/zilliqa/quorum_certificate.ex | 56 +++++ apps/explorer/lib/explorer/repo.ex | 209 +++--------------- ...240927123039_create_quorum_certificate.exs | 20 ++ ...01_create_aggregate_quorum_certificate.exs | 19 ++ ...23113_create_nested_quorum_certificate.exs | 21 ++ .../20241015095021_add_view_to_block.exs | 9 + ...anitize_duplicated_log_index_logs_test.exs | 4 + apps/indexer/lib/indexer/block/fetcher.ex | 49 ++-- config/config_helper.exs | 48 ++-- config/runtime/dev.exs | 155 +++---------- config/runtime/prod.exs | 141 +++--------- cspell.json | 5 + 41 files changed, 2218 insertions(+), 623 deletions(-) create mode 100644 .github/workflows/pre-release-zilliqa.yml create mode 100644 .github/workflows/publish-docker-image-for-zilliqa.yml create mode 100644 .github/workflows/release-zilliqa.yml create mode 100644 apps/block_scout_web/lib/block_scout_web/views/api/v2/zilliqa_view.ex create mode 100644 apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/zilliqa.ex create mode 100644 apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/zilliqa/aggregate_quorum_certificate.ex create mode 100644 apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/zilliqa/helper.ex create mode 100644 apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/zilliqa/nested_quorum_certificates.ex create mode 100644 apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/zilliqa/quorum_certificate.ex create mode 100644 apps/ethereum_jsonrpc/test/ethereum_jsonrpc/zilliqa_test.exs create mode 100644 apps/explorer/lib/explorer/chain/import/runner/zilliqa/aggregate_quorum_certificates.ex create mode 100644 apps/explorer/lib/explorer/chain/import/runner/zilliqa/nested_quorum_certificates.ex create mode 100644 apps/explorer/lib/explorer/chain/import/runner/zilliqa/quorum_certificates.ex create mode 100644 apps/explorer/lib/explorer/chain/zilliqa/aggregate_quorum_certificate.ex create mode 100644 apps/explorer/lib/explorer/chain/zilliqa/hash/signature.ex create mode 100644 apps/explorer/lib/explorer/chain/zilliqa/nested_quorum_certificate.ex create mode 100644 apps/explorer/lib/explorer/chain/zilliqa/quorum_certificate.ex create mode 100644 apps/explorer/priv/zilliqa/migrations/20240927123039_create_quorum_certificate.exs create mode 100644 apps/explorer/priv/zilliqa/migrations/20240927123101_create_aggregate_quorum_certificate.exs create mode 100644 apps/explorer/priv/zilliqa/migrations/20240927123113_create_nested_quorum_certificate.exs create mode 100644 apps/explorer/priv/zilliqa/migrations/20241015095021_add_view_to_block.exs diff --git a/.github/workflows/config.yml b/.github/workflows/config.yml index 45f887f1d5..9ec7ed3516 100644 --- a/.github/workflows/config.yml +++ b/.github/workflows/config.yml @@ -49,7 +49,24 @@ jobs: // Add/remove CI matrix chain types here const defaultChainTypes = ["default"]; - const chainTypes = ["ethereum", "polygon_zkevm", "rsk", "stability", "filecoin", "optimism", "arbitrum", "celo", "zetachain", "zksync", "shibarium", "blackfort", "scroll"]; + + const chainTypes = [ + "arbitrum", + "blackfort", + "celo", + "ethereum", + "filecoin", + "optimism", + "polygon_zkevm", + "rsk", + "scroll", + "shibarium", + "stability", + "zetachain", + "zilliqa", + "zksync" + ]; + const extraChainTypes = ["suave", "polygon_edge"]; // Chain type matrix we use in master branch diff --git a/.github/workflows/pre-release-zilliqa.yml b/.github/workflows/pre-release-zilliqa.yml new file mode 100644 index 0000000000..9b6192c64d --- /dev/null +++ b/.github/workflows/pre-release-zilliqa.yml @@ -0,0 +1,167 @@ +name: Pre-release for Zilliqa + +on: + workflow_dispatch: + inputs: + number: + type: number + required: true + +env: + OTP_VERSION: ${{ vars.OTP_VERSION }} + ELIXIR_VERSION: ${{ vars.ELIXIR_VERSION }} + +jobs: + push_to_registry: + name: Push Docker image to Docker Hub + runs-on: ubuntu-latest + env: + RELEASE_VERSION: 6.9.0 + steps: + - uses: actions/checkout@v4 + - name: Setup repo + uses: ./.github/actions/setup-repo + id: setup + with: + docker-username: ${{ secrets.DOCKER_USERNAME }} + docker-password: ${{ secrets.DOCKER_PASSWORD }} + docker-remote-multi-platform: true + docker-arm-host: ${{ secrets.ARM_RUNNER_HOSTNAME }} + docker-arm-host-key: ${{ secrets.ARM_RUNNER_KEY }} + + - name: Build and push Docker image (indexer + API) + uses: docker/build-push-action@v5 + with: + context: . + file: ./docker/Dockerfile + push: true + tags: blockscout/blockscout-zilliqa:${{ env.RELEASE_VERSION }}-alpha.${{ inputs.number }} + labels: ${{ steps.setup.outputs.docker-labels }} + platforms: | + linux/amd64 + linux/arm64/v8 + build-args: | + DISABLE_WEBAPP=false + API_V1_READ_METHODS_DISABLED=false + API_V1_WRITE_METHODS_DISABLED=false + CACHE_EXCHANGE_RATES_PERIOD= + CACHE_TOTAL_GAS_USAGE_COUNTER_ENABLED= + CACHE_ADDRESS_WITH_BALANCES_UPDATE_INTERVAL= + ADMIN_PANEL_ENABLED=false + BLOCKSCOUT_VERSION=v${{ env.RELEASE_VERSION }}-alpha.${{ inputs.number }} + RELEASE_VERSION=${{ env.RELEASE_VERSION }} + CHAIN_TYPE=zilliqa + + - name: Build and push Docker image (indexer) + uses: docker/build-push-action@v5 + with: + context: . + file: ./docker/Dockerfile + push: true + tags: blockscout/blockscout-zilliqa:${{ env.RELEASE_VERSION }}-alpha.${{ inputs.number }}-indexer + labels: ${{ steps.setup.outputs.docker-labels }} + platforms: | + linux/amd64 + linux/arm64/v8 + build-args: | + DISABLE_API=true + DISABLE_WEBAPP=true + CACHE_EXCHANGE_RATES_PERIOD= + CACHE_TOTAL_GAS_USAGE_COUNTER_ENABLED= + CACHE_ADDRESS_WITH_BALANCES_UPDATE_INTERVAL= + ADMIN_PANEL_ENABLED=false + BLOCKSCOUT_VERSION=v${{ env.RELEASE_VERSION }}-alpha.${{ inputs.number }} + RELEASE_VERSION=${{ env.RELEASE_VERSION }} + CHAIN_TYPE=zilliqa + + - name: Build and push Docker image (API) + uses: docker/build-push-action@v5 + with: + context: . + file: ./docker/Dockerfile + push: true + tags: blockscout/blockscout-zilliqa:${{ env.RELEASE_VERSION }}-alpha.${{ inputs.number }}-api + labels: ${{ steps.setup.outputs.docker-labels }} + platforms: | + linux/amd64 + linux/arm64/v8 + build-args: | + DISABLE_INDEXER=true + DISABLE_WEBAPP=true + CACHE_EXCHANGE_RATES_PERIOD= + CACHE_TOTAL_GAS_USAGE_COUNTER_ENABLED= + CACHE_ADDRESS_WITH_BALANCES_UPDATE_INTERVAL= + ADMIN_PANEL_ENABLED=false + BLOCKSCOUT_VERSION=v${{ env.RELEASE_VERSION }}-alpha.${{ inputs.number }} + RELEASE_VERSION=${{ env.RELEASE_VERSION }} + CHAIN_TYPE=zilliqa + + - name: Build and push Docker image (indexer + API + shrink internal transactions) + uses: docker/build-push-action@v5 + with: + context: . + file: ./docker/Dockerfile + push: true + tags: blockscout/blockscout-zilliqa:${{ env.RELEASE_VERSION }}-alpha.${{ inputs.number }}-shrink-internal-txs + labels: ${{ steps.setup.outputs.docker-labels }} + platforms: | + linux/amd64 + linux/arm64/v8 + build-args: | + CACHE_EXCHANGE_RATES_PERIOD= + API_V1_READ_METHODS_DISABLED=false + DISABLE_WEBAPP=false + API_V1_WRITE_METHODS_DISABLED=false + CACHE_TOTAL_GAS_USAGE_COUNTER_ENABLED= + ADMIN_PANEL_ENABLED=false + CACHE_ADDRESS_WITH_BALANCES_UPDATE_INTERVAL= + BLOCKSCOUT_VERSION=v${{ env.RELEASE_VERSION }}-alpha.${{ inputs.number }} + RELEASE_VERSION=${{ env.RELEASE_VERSION }} + CHAIN_TYPE=zilliqa + SHRINK_INTERNAL_TRANSACTIONS_ENABLED=true + + - name: Build and push Docker image (indexer + shrink internal transactions) + uses: docker/build-push-action@v5 + with: + context: . + file: ./docker/Dockerfile + push: true + tags: blockscout/blockscout-zilliqa:${{ env.RELEASE_VERSION }}-alpha.${{ inputs.number }}-shrink-internal-txs-indexer + labels: ${{ steps.setup.outputs.docker-labels }} + platforms: | + linux/amd64 + linux/arm64/v8 + build-args: | + CACHE_EXCHANGE_RATES_PERIOD= + DISABLE_WEBAPP=true + DISABLE_API=true + CACHE_TOTAL_GAS_USAGE_COUNTER_ENABLED= + ADMIN_PANEL_ENABLED=false + CACHE_ADDRESS_WITH_BALANCES_UPDATE_INTERVAL= + BLOCKSCOUT_VERSION=v${{ env.RELEASE_VERSION }}-alpha.${{ inputs.number }} + RELEASE_VERSION=${{ env.RELEASE_VERSION }} + CHAIN_TYPE=zilliqa + SHRINK_INTERNAL_TRANSACTIONS_ENABLED=true + + - name: Build and push Docker image (API + shrink internal transactions) + uses: docker/build-push-action@v5 + with: + context: . + file: ./docker/Dockerfile + push: true + tags: blockscout/blockscout-zilliqa:${{ env.RELEASE_VERSION }}-alpha.${{ inputs.number }}-shrink-internal-txs-api + labels: ${{ steps.setup.outputs.docker-labels }} + platforms: | + linux/amd64 + linux/arm64/v8 + build-args: | + CACHE_EXCHANGE_RATES_PERIOD= + DISABLE_WEBAPP=true + DISABLE_INDEXER=true + CACHE_TOTAL_GAS_USAGE_COUNTER_ENABLED= + ADMIN_PANEL_ENABLED=false + CACHE_ADDRESS_WITH_BALANCES_UPDATE_INTERVAL= + BLOCKSCOUT_VERSION=v${{ env.RELEASE_VERSION }}-alpha.${{ inputs.number }} + RELEASE_VERSION=${{ env.RELEASE_VERSION }} + CHAIN_TYPE=zilliqa + SHRINK_INTERNAL_TRANSACTIONS_ENABLED=true diff --git a/.github/workflows/publish-docker-image-for-zilliqa.yml b/.github/workflows/publish-docker-image-for-zilliqa.yml new file mode 100644 index 0000000000..68dd5ce6e5 --- /dev/null +++ b/.github/workflows/publish-docker-image-for-zilliqa.yml @@ -0,0 +1,162 @@ +name: Zilliqa publish Docker image + +on: + workflow_dispatch: + push: + branches: + - production-zilliqa +jobs: + push_to_registry: + name: Push Docker image to Docker Hub + runs-on: ubuntu-latest + env: + RELEASE_VERSION: 6.9.0 + DOCKER_CHAIN_NAME: zilliqa + steps: + - uses: actions/checkout@v4 + - name: Setup repo + uses: ./.github/actions/setup-repo + id: setup + with: + docker-username: ${{ secrets.DOCKER_USERNAME }} + docker-password: ${{ secrets.DOCKER_PASSWORD }} + docker-remote-multi-platform: true + docker-arm-host: ${{ secrets.ARM_RUNNER_HOSTNAME }} + docker-arm-host-key: ${{ secrets.ARM_RUNNER_KEY }} + + - name: Build and push Docker image (indexer + API) + uses: docker/build-push-action@v5 + with: + context: . + file: ./docker/Dockerfile + push: true + tags: blockscout/blockscout-${{ env.DOCKER_CHAIN_NAME }}:${{ env.RELEASE_VERSION }}-postrelease-${{ env.SHORT_SHA }} + labels: ${{ steps.setup.outputs.docker-labels }} + platforms: | + linux/amd64 + linux/arm64/v8 + build-args: | + CACHE_EXCHANGE_RATES_PERIOD= + API_V1_READ_METHODS_DISABLED=false + DISABLE_WEBAPP=false + API_V1_WRITE_METHODS_DISABLED=false + CACHE_TOTAL_GAS_USAGE_COUNTER_ENABLED= + ADMIN_PANEL_ENABLED=false + CACHE_ADDRESS_WITH_BALANCES_UPDATE_INTERVAL= + BLOCKSCOUT_VERSION=v${{ env.RELEASE_VERSION }}-beta.+commit.${{ env.SHORT_SHA }} + RELEASE_VERSION=${{ env.RELEASE_VERSION }} + CHAIN_TYPE=${{ env.DOCKER_CHAIN_NAME }} + + - name: Build and push Docker image (indexer) + uses: docker/build-push-action@v5 + with: + context: . + file: ./docker/Dockerfile + push: true + tags: blockscout/blockscout-${{ env.DOCKER_CHAIN_NAME }}:${{ env.RELEASE_VERSION }}-postrelease-${{ env.SHORT_SHA }}-indexer + labels: ${{ steps.setup.outputs.docker-labels }} + platforms: | + linux/amd64 + linux/arm64/v8 + build-args: | + CACHE_EXCHANGE_RATES_PERIOD= + DISABLE_WEBAPP=true + DISABLE_API=true + CACHE_TOTAL_GAS_USAGE_COUNTER_ENABLED= + ADMIN_PANEL_ENABLED=false + CACHE_ADDRESS_WITH_BALANCES_UPDATE_INTERVAL= + BLOCKSCOUT_VERSION=v${{ env.RELEASE_VERSION }}-beta.+commit.${{ env.SHORT_SHA }} + RELEASE_VERSION=${{ env.RELEASE_VERSION }} + CHAIN_TYPE=${{ env.DOCKER_CHAIN_NAME }} + + - name: Build and push Docker image (API) + uses: docker/build-push-action@v5 + with: + context: . + file: ./docker/Dockerfile + push: true + tags: blockscout/blockscout-${{ env.DOCKER_CHAIN_NAME }}:${{ env.RELEASE_VERSION }}-postrelease-${{ env.SHORT_SHA }}-api + labels: ${{ steps.setup.outputs.docker-labels }} + platforms: | + linux/amd64 + linux/arm64/v8 + build-args: | + CACHE_EXCHANGE_RATES_PERIOD= + DISABLE_WEBAPP=true + DISABLE_INDEXER=true + CACHE_TOTAL_GAS_USAGE_COUNTER_ENABLED= + ADMIN_PANEL_ENABLED=false + CACHE_ADDRESS_WITH_BALANCES_UPDATE_INTERVAL= + BLOCKSCOUT_VERSION=v${{ env.RELEASE_VERSION }}-beta.+commit.${{ env.SHORT_SHA }} + RELEASE_VERSION=${{ env.RELEASE_VERSION }} + CHAIN_TYPE=${{ env.DOCKER_CHAIN_NAME }} + + - name: Build and push Docker image (indexer + API + shrink internal transactions) + uses: docker/build-push-action@v5 + with: + context: . + file: ./docker/Dockerfile + push: true + tags: blockscout/blockscout-${{ env.DOCKER_CHAIN_NAME }}:${{ env.RELEASE_VERSION }}-postrelease-${{ env.SHORT_SHA }}-shrink-internal-txs + labels: ${{ steps.setup.outputs.docker-labels }} + platforms: | + linux/amd64 + linux/arm64/v8 + build-args: | + CACHE_EXCHANGE_RATES_PERIOD= + API_V1_READ_METHODS_DISABLED=false + DISABLE_WEBAPP=false + API_V1_WRITE_METHODS_DISABLED=false + CACHE_TOTAL_GAS_USAGE_COUNTER_ENABLED= + ADMIN_PANEL_ENABLED=false + CACHE_ADDRESS_WITH_BALANCES_UPDATE_INTERVAL= + BLOCKSCOUT_VERSION=v${{ env.RELEASE_VERSION }}-beta.+commit.${{ env.SHORT_SHA }} + RELEASE_VERSION=${{ env.RELEASE_VERSION }} + CHAIN_TYPE=${{ env.DOCKER_CHAIN_NAME }} + SHRINK_INTERNAL_TRANSACTIONS_ENABLED=true + + - name: Build and push Docker image (indexer + shrink internal transactions) + uses: docker/build-push-action@v5 + with: + context: . + file: ./docker/Dockerfile + push: true + tags: blockscout/blockscout-${{ env.DOCKER_CHAIN_NAME }}:${{ env.RELEASE_VERSION }}-postrelease-${{ env.SHORT_SHA }}-shrink-internal-txs-indexer + labels: ${{ steps.setup.outputs.docker-labels }} + platforms: | + linux/amd64 + linux/arm64/v8 + build-args: | + CACHE_EXCHANGE_RATES_PERIOD= + DISABLE_WEBAPP=true + DISABLE_API=true + CACHE_TOTAL_GAS_USAGE_COUNTER_ENABLED= + ADMIN_PANEL_ENABLED=false + CACHE_ADDRESS_WITH_BALANCES_UPDATE_INTERVAL= + BLOCKSCOUT_VERSION=v${{ env.RELEASE_VERSION }}-beta.+commit.${{ env.SHORT_SHA }} + RELEASE_VERSION=${{ env.RELEASE_VERSION }} + CHAIN_TYPE=${{ env.DOCKER_CHAIN_NAME }} + SHRINK_INTERNAL_TRANSACTIONS_ENABLED=true + + - name: Build and push Docker image (API + shrink internal transactions) + uses: docker/build-push-action@v5 + with: + context: . + file: ./docker/Dockerfile + push: true + tags: blockscout/blockscout-${{ env.DOCKER_CHAIN_NAME }}:${{ env.RELEASE_VERSION }}-postrelease-${{ env.SHORT_SHA }}-shrink-internal-txs-api + labels: ${{ steps.setup.outputs.docker-labels }} + platforms: | + linux/amd64 + linux/arm64/v8 + build-args: | + CACHE_EXCHANGE_RATES_PERIOD= + DISABLE_WEBAPP=true + DISABLE_INDEXER=true + CACHE_TOTAL_GAS_USAGE_COUNTER_ENABLED= + ADMIN_PANEL_ENABLED=false + CACHE_ADDRESS_WITH_BALANCES_UPDATE_INTERVAL= + BLOCKSCOUT_VERSION=v${{ env.RELEASE_VERSION }}-beta.+commit.${{ env.SHORT_SHA }} + RELEASE_VERSION=${{ env.RELEASE_VERSION }} + CHAIN_TYPE=${{ env.DOCKER_CHAIN_NAME }} + SHRINK_INTERNAL_TRANSACTIONS_ENABLED=true diff --git a/.github/workflows/release-zilliqa.yml b/.github/workflows/release-zilliqa.yml new file mode 100644 index 0000000000..9c5d82d5ba --- /dev/null +++ b/.github/workflows/release-zilliqa.yml @@ -0,0 +1,164 @@ +name: Release for Zilliqa + +on: + release: + types: [published] + +env: + OTP_VERSION: ${{ vars.OTP_VERSION }} + ELIXIR_VERSION: ${{ vars.ELIXIR_VERSION }} + +jobs: + push_to_registry: + name: Push Docker image to Docker Hub + runs-on: ubuntu-latest + env: + RELEASE_VERSION: 6.9.0 + steps: + - uses: actions/checkout@v4 + - name: Setup repo + uses: ./.github/actions/setup-repo + id: setup + with: + docker-username: ${{ secrets.DOCKER_USERNAME }} + docker-password: ${{ secrets.DOCKER_PASSWORD }} + docker-remote-multi-platform: true + docker-arm-host: ${{ secrets.ARM_RUNNER_HOSTNAME }} + docker-arm-host-key: ${{ secrets.ARM_RUNNER_KEY }} + + - name: Build and push Docker image (indexer + API) + uses: docker/build-push-action@v5 + with: + context: . + file: ./docker/Dockerfile + push: true + tags: blockscout/blockscout-zilliqa:latest, blockscout/blockscout-zilliqa:${{ env.RELEASE_VERSION }} + labels: ${{ steps.setup.outputs.docker-labels }} + platforms: | + linux/amd64 + linux/arm64/v8 + build-args: | + DISABLE_WEBAPP=false + API_V1_READ_METHODS_DISABLED=false + API_V1_WRITE_METHODS_DISABLED=false + CACHE_EXCHANGE_RATES_PERIOD= + CACHE_TOTAL_GAS_USAGE_COUNTER_ENABLED= + CACHE_ADDRESS_WITH_BALANCES_UPDATE_INTERVAL= + ADMIN_PANEL_ENABLED=false + BLOCKSCOUT_VERSION=v${{ env.RELEASE_VERSION }}-beta + RELEASE_VERSION=${{ env.RELEASE_VERSION }} + CHAIN_TYPE=zilliqa + + - name: Build and push Docker image (indexer) + uses: docker/build-push-action@v5 + with: + context: . + file: ./docker/Dockerfile + push: true + tags: blockscout/blockscout-zilliqa:${{ env.RELEASE_VERSION }}-indexer + labels: ${{ steps.setup.outputs.docker-labels }} + platforms: | + linux/amd64 + linux/arm64/v8 + build-args: | + DISABLE_API=true + DISABLE_WEBAPP=true + CACHE_EXCHANGE_RATES_PERIOD= + CACHE_TOTAL_GAS_USAGE_COUNTER_ENABLED= + CACHE_ADDRESS_WITH_BALANCES_UPDATE_INTERVAL= + ADMIN_PANEL_ENABLED=false + BLOCKSCOUT_VERSION=v${{ env.RELEASE_VERSION }}-beta + RELEASE_VERSION=${{ env.RELEASE_VERSION }} + CHAIN_TYPE=zilliqa + + - name: Build and push Docker image (API) + uses: docker/build-push-action@v5 + with: + context: . + file: ./docker/Dockerfile + push: true + tags: blockscout/blockscout-zilliqa:${{ env.RELEASE_VERSION }}-api + labels: ${{ steps.setup.outputs.docker-labels }} + platforms: | + linux/amd64 + linux/arm64/v8 + build-args: | + DISABLE_INDEXER=true + DISABLE_WEBAPP=true + CACHE_EXCHANGE_RATES_PERIOD= + CACHE_TOTAL_GAS_USAGE_COUNTER_ENABLED= + CACHE_ADDRESS_WITH_BALANCES_UPDATE_INTERVAL= + ADMIN_PANEL_ENABLED=false + BLOCKSCOUT_VERSION=v${{ env.RELEASE_VERSION }}-beta + RELEASE_VERSION=${{ env.RELEASE_VERSION }} + CHAIN_TYPE=zilliqa + + - name: Build and push Docker image (indexer + API + shrink internal transactions) + uses: docker/build-push-action@v5 + with: + context: . + file: ./docker/Dockerfile + push: true + tags: blockscout/blockscout-zilliqa:${{ env.RELEASE_VERSION }}-shrink-internal-txs + labels: ${{ steps.setup.outputs.docker-labels }} + platforms: | + linux/amd64 + linux/arm64/v8 + build-args: | + CACHE_EXCHANGE_RATES_PERIOD= + API_V1_READ_METHODS_DISABLED=false + DISABLE_WEBAPP=false + API_V1_WRITE_METHODS_DISABLED=false + CACHE_TOTAL_GAS_USAGE_COUNTER_ENABLED= + ADMIN_PANEL_ENABLED=false + CACHE_ADDRESS_WITH_BALANCES_UPDATE_INTERVAL= + BLOCKSCOUT_VERSION=v${{ env.RELEASE_VERSION }}-beta + RELEASE_VERSION=${{ env.RELEASE_VERSION }} + CHAIN_TYPE=zilliqa + SHRINK_INTERNAL_TRANSACTIONS_ENABLED=true + + - name: Build and push Docker image (indexer + shrink internal transactions) + uses: docker/build-push-action@v5 + with: + context: . + file: ./docker/Dockerfile + push: true + tags: blockscout/blockscout-zilliqa:${{ env.RELEASE_VERSION }}-shrink-internal-txs-indexer + labels: ${{ steps.setup.outputs.docker-labels }} + platforms: | + linux/amd64 + linux/arm64/v8 + build-args: | + CACHE_EXCHANGE_RATES_PERIOD= + DISABLE_WEBAPP=true + DISABLE_API=true + CACHE_TOTAL_GAS_USAGE_COUNTER_ENABLED= + ADMIN_PANEL_ENABLED=false + CACHE_ADDRESS_WITH_BALANCES_UPDATE_INTERVAL= + BLOCKSCOUT_VERSION=v${{ env.RELEASE_VERSION }}-beta + RELEASE_VERSION=${{ env.RELEASE_VERSION }} + CHAIN_TYPE=zilliqa + SHRINK_INTERNAL_TRANSACTIONS_ENABLED=true + + - name: Build and push Docker image (API + shrink internal transactions) + uses: docker/build-push-action@v5 + with: + context: . + file: ./docker/Dockerfile + push: true + tags: blockscout/blockscout-zilliqa:${{ env.RELEASE_VERSION }}-shrink-internal-txs-api + labels: ${{ steps.setup.outputs.docker-labels }} + platforms: | + linux/amd64 + linux/arm64/v8 + build-args: | + CACHE_EXCHANGE_RATES_PERIOD= + DISABLE_WEBAPP=true + DISABLE_INDEXER=true + CACHE_TOTAL_GAS_USAGE_COUNTER_ENABLED= + ADMIN_PANEL_ENABLED=false + CACHE_ADDRESS_WITH_BALANCES_UPDATE_INTERVAL= + BLOCKSCOUT_VERSION=v${{ env.RELEASE_VERSION }}-beta + RELEASE_VERSION=${{ env.RELEASE_VERSION }} + CHAIN_TYPE=zilliqa + SHRINK_INTERNAL_TRANSACTIONS_ENABLED=true diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/block_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/block_controller.ex index ba6bcd1e3f..d7a3164b47 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/block_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/block_controller.ex @@ -76,6 +76,14 @@ defmodule BlockScoutWeb.API.V2.BlockController do :arbitrum_confirmation_transaction => :optional } + :zilliqa -> + @chain_type_transaction_necessity_by_association %{} + @chain_type_block_necessity_by_association %{ + :zilliqa_quorum_certificate => :optional, + :zilliqa_aggregate_quorum_certificate => :optional, + [zilliqa_aggregate_quorum_certificate: [:nested_quorum_certificates]] => :optional + } + _ -> @chain_type_transaction_necessity_by_association %{} @chain_type_block_necessity_by_association %{} diff --git a/apps/block_scout_web/lib/block_scout_web/views/api/v2/block_view.ex b/apps/block_scout_web/lib/block_scout_web/views/api/v2/block_view.ex index 6f9b2ccc48..79f3abdd67 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/api/v2/block_view.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/api/v2/block_view.ex @@ -156,6 +156,12 @@ defmodule BlockScoutWeb.API.V2.BlockView do BlockScoutWeb.API.V2.CeloView.extend_block_json_response(result, block, single_block?) end + :zilliqa -> + defp chain_type_fields(result, block, single_block?) do + # credo:disable-for-next-line Credo.Check.Design.AliasUsage + BlockScoutWeb.API.V2.ZilliqaView.extend_block_json_response(result, block, single_block?) + end + _ -> defp chain_type_fields(result, _block, _single_block?) do result diff --git a/apps/block_scout_web/lib/block_scout_web/views/api/v2/zilliqa_view.ex b/apps/block_scout_web/lib/block_scout_web/views/api/v2/zilliqa_view.ex new file mode 100644 index 0000000000..bccc41f1eb --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/views/api/v2/zilliqa_view.ex @@ -0,0 +1,88 @@ +if Application.compile_env(:explorer, :chain_type) == :zilliqa do + defmodule BlockScoutWeb.API.V2.ZilliqaView do + @moduledoc """ + View functions for rendering Zilliqa-related data in JSON format. + """ + + alias Explorer.Chain.Block + alias Explorer.Chain.Zilliqa.{AggregateQuorumCertificate, QuorumCertificate} + + @doc """ + Extends the JSON output with a sub-map containing information related to Zilliqa, + such as the quorum certificate and aggregate quorum certificate. + + ## Parameters + - `out_json`: A map defining the output JSON which will be extended. + - `block`: The block structure containing Zilliqa-related data. + - `single_block?`: A boolean indicating if it is a single block. + + ## Returns + - A map extended with data related to Zilliqa. + """ + @spec extend_block_json_response(map(), Block.t(), boolean()) :: map() + def extend_block_json_response(out_json, %Block{}, false), + do: out_json + + def extend_block_json_response(out_json, %Block{zilliqa_view: zilliqa_view} = block, true) do + zilliqa_json = + %{view: zilliqa_view} + |> add_quorum_certificate(block) + |> add_aggregate_quorum_certificate(block) + + Map.put(out_json, :zilliqa, zilliqa_json) + end + + @spec add_quorum_certificate(map(), Block.t()) :: map() + defp add_quorum_certificate( + zilliqa_json, + %Block{ + zilliqa_quorum_certificate: %QuorumCertificate{ + view: view, + signature: signature, + signers: signers + } + } + ) do + zilliqa_json + |> Map.put(:quorum_certificate, %{ + view: view, + signature: signature, + signers: signers + }) + end + + defp add_quorum_certificate(zilliqa_json, _block), do: zilliqa_json + + @spec add_aggregate_quorum_certificate(map(), Block.t()) :: map() + defp add_aggregate_quorum_certificate(zilliqa_json, %Block{ + zilliqa_aggregate_quorum_certificate: %AggregateQuorumCertificate{ + view: view, + signature: signature, + nested_quorum_certificates: nested_quorum_certificates + } + }) + when is_list(nested_quorum_certificates) do + zilliqa_json + |> Map.put(:aggregate_quorum_certificate, %{ + view: view, + signature: signature, + signers: + Enum.map( + nested_quorum_certificates, + & &1.proposed_by_validator_index + ), + nested_quorum_certificates: + Enum.map( + nested_quorum_certificates, + &%{ + view: &1.view, + signature: &1.signature, + proposed_by_validator_index: &1.proposed_by_validator_index + } + ) + }) + end + + defp add_aggregate_quorum_certificate(zilliqa_json, _block), do: zilliqa_json + end +end diff --git a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/block.ex b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/block.ex index 9b38692560..1024cb8062 100644 --- a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/block.ex +++ b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/block.ex @@ -8,6 +8,9 @@ defmodule EthereumJSONRPC.Block do alias EthereumJSONRPC.{Transactions, Uncles, Withdrawals} + alias EthereumJSONRPC.Zilliqa.AggregateQuorumCertificate, as: ZilliqaAggregateQuorumCertificate + alias EthereumJSONRPC.Zilliqa.QuorumCertificate, as: ZilliqaQuorumCertificate + # Because proof of stake does not naturally produce uncles like proof of work, # the list of these in each block is empty, and the hash of this list # (sha3Uncles) is the RLP-encoded hash of an empty list. @@ -17,29 +20,36 @@ defmodule EthereumJSONRPC.Block do :rsk -> @chain_type_fields quote( do: [ - bitcoin_merged_mining_header: EthereumJSONRPC.data(), - bitcoin_merged_mining_coinbase_transaction: EthereumJSONRPC.data(), - bitcoin_merged_mining_merkle_proof: EthereumJSONRPC.data(), - hash_for_merged_mining: EthereumJSONRPC.data(), - minimum_gas_price: non_neg_integer() + {optional(:bitcoin_merged_mining_header), EthereumJSONRPC.data()}, + {optional(:bitcoin_merged_mining_coinbase_transaction), EthereumJSONRPC.data()}, + {optional(:bitcoin_merged_mining_merkle_proof), EthereumJSONRPC.data()}, + {optional(:hash_for_merged_mining), EthereumJSONRPC.data()}, + {optional(:minimum_gas_price), non_neg_integer()} ] ) :ethereum -> @chain_type_fields quote( do: [ - withdrawals_root: EthereumJSONRPC.hash(), - blob_gas_used: non_neg_integer(), - excess_blob_gas: non_neg_integer() + {optional(:withdrawals_root), EthereumJSONRPC.hash()}, + {optional(:blob_gas_used), non_neg_integer()}, + {optional(:excess_blob_gas), non_neg_integer()} ] ) :arbitrum -> @chain_type_fields quote( do: [ - send_count: non_neg_integer(), - send_root: EthereumJSONRPC.hash(), - l1_block_number: non_neg_integer() + {optional(:send_count), non_neg_integer()}, + {optional(:send_root), EthereumJSONRPC.hash()}, + {optional(:l1_block_number), non_neg_integer()} + ] + ) + + :zilliqa -> + @chain_type_fields quote( + do: [ + {optional(:zilliqa_view), non_neg_integer()} ] ) @@ -47,7 +57,14 @@ defmodule EthereumJSONRPC.Block do @chain_type_fields quote(do: []) end - @type elixir :: %{String.t() => non_neg_integer | DateTime.t() | String.t() | nil} + @type elixir :: %{ + String.t() => + non_neg_integer + | DateTime.t() + | String.t() + | map() + | nil + } @type params :: %{ unquote_splicing(@chain_type_fields), difficulty: pos_integer(), @@ -191,6 +208,15 @@ defmodule EthereumJSONRPC.Block do "sendCount" => 91,\ "l1BlockNumber" => 19828534,\ """ + :zilliqa -> """ + "view" => "0x115cca",\ + "quorumCertificate" => %{\ + "block_hash" => "0x4b8939a7fb0d7de4b288bafd4d5caa02f53abf3c1e348fca5038eebbf68248fa",\ + "cosigned" => "[1, 1, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]",\ + "signature" => "0xa78c7f3e07e1df963ddeda17a1e5afd97c7c8a6fc8e0616249c22a2a1cc91f8eef6073cab8ba22b50cc7b38090f1ad9109473d30f24d57858d1f28c6679b3c4deeb800e5572b5e15604596594d506d3103a44d8b707da581f1a4b82310aeecb6",\ + "view" => "0x115cc7"\ + },\ + """ _ -> "" end} ...> "uncles" => [] @@ -233,6 +259,9 @@ defmodule EthereumJSONRPC.Block do send_count: 91,\ l1_block_number: 19828534,\ """ + :zilliqa -> """ + zilliqa_view: "0x115cca",\ + """ _ -> "" end} uncles: [] @@ -301,6 +330,9 @@ defmodule EthereumJSONRPC.Block do send_count: nil,\ l1_block_number: nil,\ """ + :zilliqa -> """ + zilliqa_view: nil,\ + """ _ -> "" end} uncles: [] @@ -490,6 +522,7 @@ defmodule EthereumJSONRPC.Block do } end + @spec chain_type_fields(params, elixir) :: params case Application.compile_env(:explorer, :chain_type) do :rsk -> defp chain_type_fields(params, elixir) do @@ -524,6 +557,14 @@ defmodule EthereumJSONRPC.Block do }) end + :zilliqa -> + defp chain_type_fields(params, elixir) do + params + |> Map.merge(%{ + zilliqa_view: Map.get(elixir, "view") + }) + end + _ -> defp chain_type_fields(params, _), do: params end @@ -733,6 +774,30 @@ defmodule EthereumJSONRPC.Block do def elixir_to_withdrawals(%{"withdrawals" => withdrawals}), do: withdrawals def elixir_to_withdrawals(_), do: [] + @doc """ + Get `t:EthereumJSONRPC.Zilliqa.QuorumCertificate.elixir/0` from `t:elixir/0`. + """ + @spec elixir_to_zilliqa_quorum_certificate(elixir()) :: ZilliqaQuorumCertificate.t() | nil + def elixir_to_zilliqa_quorum_certificate(%{"quorumCertificate" => quorum_certificate}), + do: quorum_certificate + + # WARN: This clause is introduced as a workaround to fix tests. HOWEVER, it + # allows the block with a `quorumCertificate` field to be successfully + # imported. This is a temporary solution and should be addressed in the future. + def elixir_to_zilliqa_quorum_certificate(_), do: nil + + @doc """ + Get `t:EthereumJSONRPC.Zilliqa.AggregateQuorumCertificate.elixir/0` from `t:elixir/0`. + """ + @spec elixir_to_zilliqa_aggregate_quorum_certificate(elixir()) :: + ZilliqaAggregateQuorumCertificate.t() | nil + def elixir_to_zilliqa_aggregate_quorum_certificate(%{ + "aggregateQuorumCertificate" => aggregate_quorum_certificate + }), + do: aggregate_quorum_certificate + + def elixir_to_zilliqa_aggregate_quorum_certificate(_), do: nil + @doc """ Decodes the stringly typed numerical fields to `t:non_neg_integer/0` and the timestamps to `t:DateTime.t/0` @@ -834,7 +899,7 @@ defmodule EthereumJSONRPC.Block do defp entry_to_elixir({key, quantity}, _block) when key in ~w(difficulty gasLimit gasUsed minimumGasPrice baseFeePerGas number size - cumulativeDifficulty totalDifficulty paidFees minimumGasPrice blobGasUsed + cumulativeDifficulty totalDifficulty paidFees blobGasUsed excessBlobGas l1BlockNumber sendCount) and not is_nil(quantity) do {key, quantity_to_integer(quantity)} @@ -872,6 +937,26 @@ defmodule EthereumJSONRPC.Block do {key, Withdrawals.to_elixir(withdrawals, block_hash, quantity_to_integer(block_number))} end + case Application.compile_env(:explorer, :chain_type) do + :zilliqa -> + defp entry_to_elixir({"view" = key, quantity}, _block) when not is_nil(quantity) do + {key, quantity_to_integer(quantity)} + end + + defp entry_to_elixir({"quorumCertificate" = key, entry}, %{"hash" => block_hash}) do + # credo:disable-for-next-line Credo.Check.Design.AliasUsage + {key, EthereumJSONRPC.Zilliqa.QuorumCertificate.new(entry, block_hash)} + end + + defp entry_to_elixir({"aggregateQuorumCertificate" = key, entry}, %{"hash" => block_hash}) do + # credo:disable-for-next-line Credo.Check.Design.AliasUsage + {key, EthereumJSONRPC.Zilliqa.AggregateQuorumCertificate.new(entry, block_hash)} + end + + _ -> + :ok + end + # bitcoinMergedMiningCoinbaseTransaction bitcoinMergedMiningHeader bitcoinMergedMiningMerkleProof hashForMergedMining - RSK https://github.com/blockscout/blockscout/pull/2934 # committedSeals committee pastCommittedSeals proposerSeal round - Autonity network https://github.com/blockscout/blockscout/pull/3480 # blockGasCost extDataGasUsed - sgb/ava https://github.com/blockscout/blockscout/pull/5301 diff --git a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/blocks.ex b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/blocks.ex index eb2b14dd45..9439089f60 100644 --- a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/blocks.ex +++ b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/blocks.ex @@ -8,7 +8,44 @@ defmodule EthereumJSONRPC.Blocks do @type elixir :: [Block.elixir()] @type params :: [Block.params()] + + @default_struct_fields [ + blocks_params: [], + block_second_degree_relations_params: [], + transactions_params: [], + withdrawals_params: [], + errors: [] + ] + + case Application.compile_env(:explorer, :chain_type) do + :zilliqa -> + @chain_type_fields quote( + do: [ + zilliqa_quorum_certificates_params: [ + EthereumJSONRPC.Zilliqa.QuorumCertificate.params() + ], + zilliqa_aggregate_quorum_certificates_params: [ + EthereumJSONRPC.Zilliqa.AggregateQuorumCertificate.params() + ], + zilliqa_nested_quorum_certificates_params: [ + EthereumJSONRPC.Zilliqa.NestedQuorumCertificates.params() + ] + ] + ) + + @chain_type_struct_fields [ + zilliqa_quorum_certificates_params: [], + zilliqa_aggregate_quorum_certificates_params: [], + zilliqa_nested_quorum_certificates_params: [] + ] + + _ -> + @chain_type_struct_fields [] + @chain_type_fields quote(do: []) + end + @type t :: %__MODULE__{ + unquote_splicing(@chain_type_fields), blocks_params: [map()], block_second_degree_relations_params: [map()], transactions_params: [map()], @@ -16,11 +53,7 @@ defmodule EthereumJSONRPC.Blocks do errors: [Transport.error()] } - defstruct blocks_params: [], - block_second_degree_relations_params: [], - transactions_params: [], - withdrawals_params: [], - errors: [] + defstruct @default_struct_fields ++ @chain_type_struct_fields def requests(id_to_params, request) when is_map(id_to_params) and is_function(request, 1) do Enum.map(id_to_params, fn {id, params} -> @@ -62,6 +95,21 @@ defmodule EthereumJSONRPC.Blocks do transactions_params: transactions_params, withdrawals_params: withdrawals_params } + |> extend_with_chain_type_fields(elixir_blocks) + end + + @spec extend_with_chain_type_fields(t(), elixir()) :: t() + case Application.compile_env(:explorer, :chain_type) do + :zilliqa -> + defp extend_with_chain_type_fields(%__MODULE__{} = blocks, elixir_blocks) do + # credo:disable-for-next-line Credo.Check.Design.AliasUsage + EthereumJSONRPC.Zilliqa.Helper.extend_blocks_struct(blocks, elixir_blocks) + end + + _ -> + defp extend_with_chain_type_fields(%__MODULE__{} = blocks, _elixir_blocks) do + blocks + end end @doc """ @@ -134,6 +182,9 @@ defmodule EthereumJSONRPC.Blocks do send_count: nil,\ l1_block_number: nil,\ """ + :zilliqa -> """ + zilliqa_view: nil,\ + """ _ -> "" end} uncles: ["0xe670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d15273311"] diff --git a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/zilliqa.ex b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/zilliqa.ex new file mode 100644 index 0000000000..74e0d985d0 --- /dev/null +++ b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/zilliqa.ex @@ -0,0 +1,20 @@ +defmodule EthereumJSONRPC.Zilliqa do + @moduledoc """ + Zilliqa type definitions. + """ + alias EthereumJSONRPC.Zilliqa.{ + AggregateQuorumCertificate, + NestedQuorumCertificates, + QuorumCertificate + } + + @type consensus_data_params :: %{ + zilliqa_quorum_certificates_params: [QuorumCertificate.params()], + zilliqa_aggregate_quorum_certificates_params: [AggregateQuorumCertificate.params()], + zilliqa_nested_quorum_certificates_params: [NestedQuorumCertificates.params()] + } + + @type bit_vector :: String.t() + @type validator_index :: non_neg_integer() + @type signers :: [validator_index()] +end diff --git a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/zilliqa/aggregate_quorum_certificate.ex b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/zilliqa/aggregate_quorum_certificate.ex new file mode 100644 index 0000000000..009dfc75e9 --- /dev/null +++ b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/zilliqa/aggregate_quorum_certificate.ex @@ -0,0 +1,186 @@ +defmodule EthereumJSONRPC.Zilliqa.AggregateQuorumCertificate do + @moduledoc """ + Represents an aggregate quorum certificate associated with the block. + """ + import EthereumJSONRPC, only: [quantity_to_integer: 1] + + alias EthereumJSONRPC.Zilliqa.NestedQuorumCertificates + + @type elixir :: %{ + String.t() => + EthereumJSONRPC.quantity() + | EthereumJSONRPC.hash() + | EthereumJSONRPC.Zilliqa.bit_vector() + | NestedQuorumCertificates.elixir() + } + + @type t :: %__MODULE__{ + block_hash: EthereumJSONRPC.hash(), + view: non_neg_integer(), + signature: EthereumJSONRPC.hash(), + quorum_certificates: NestedQuorumCertificates.t() + } + + @type params :: %{ + block_hash: EthereumJSONRPC.hash(), + view: non_neg_integer(), + signature: EthereumJSONRPC.hash() + } + + defstruct [:block_hash, :view, :signature, :quorum_certificates] + + @doc """ + Decodes the JSON object returned by JSONRPC node into the `t:t/0` format. + + ## Examples + + iex> aqc_json = %{ + ...> "cosigned" => "[0, 1, 1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]", + ...> "quorum_certificates" => [ + ...> %{ + ...> "block_hash" => "0x4b8939a7fb0d7de4b288bafd4d5caa02f53abf3c1e348fca5038eebbf68248fa", + ...> "cosigned" => "[1, 1, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]", + ...> "signature" => "0xa78c7f3e07e1df963ddeda17a1e5afd97c7c8a6fc8e0616249c22a2a1cc91f8eef6073cab8ba22b50cc7b38090f1ad9109473d30f24d57858d1f28c6679b3c4deeb800e5572b5e15604596594d506d3103a44d8b707da581f1a4b82310aeecb6", + ...> "view" => "0x115cc7" + ...> }, + ...> %{ + ...> "block_hash" => "0x4b8939a7fb0d7de4b288bafd4d5caa02f53abf3c1e348fca5038eebbf68248fa", + ...> "cosigned" => "[1, 1, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]", + ...> "signature" => "0xa78c7f3e07e1df963ddeda17a1e5afd97c7c8a6fc8e0616249c22a2a1cc91f8eef6073cab8ba22b50cc7b38090f1ad9109473d30f24d57858d1f28c6679b3c4deeb800e5572b5e15604596594d506d3103a44d8b707da581f1a4b82310aeecb6", + ...> "view" => "0x115cc7" + ...> }, + ...> %{ + ...> "block_hash" => "0x4b8939a7fb0d7de4b288bafd4d5caa02f53abf3c1e348fca5038eebbf68248fa", + ...> "cosigned" => "[1, 1, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]", + ...> "signature" => "0xa78c7f3e07e1df963ddeda17a1e5afd97c7c8a6fc8e0616249c22a2a1cc91f8eef6073cab8ba22b50cc7b38090f1ad9109473d30f24d57858d1f28c6679b3c4deeb800e5572b5e15604596594d506d3103a44d8b707da581f1a4b82310aeecb6", + ...> "view" => "0x115cc7" + ...> }, + ...> %{ + ...> "block_hash" => "0x4b8939a7fb0d7de4b288bafd4d5caa02f53abf3c1e348fca5038eebbf68248fa", + ...> "cosigned" => "[1, 1, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]", + ...> "signature" => "0xa78c7f3e07e1df963ddeda17a1e5afd97c7c8a6fc8e0616249c22a2a1cc91f8eef6073cab8ba22b50cc7b38090f1ad9109473d30f24d57858d1f28c6679b3c4deeb800e5572b5e15604596594d506d3103a44d8b707da581f1a4b82310aeecb6", + ...> "view" => "0x115cc7" + ...> } + ...> ], + ...> "signature" => "0x820f591cd78b29a69ba25bc85c4327fa3b0adb61a73a4f0bd943b4ab0b97e061eae9ac032d19fbfab7efb89fac2454ab0b89fea83185c0dac749ff55b0e2c21535a2b712872491577728db868d11939461a6bfde0d94d238f46b643bbe19767e", + ...> "view" => "0x115cca" + ...> } + iex> block_hash = "0x9c8a047e40ea975cb14c5ccff232a2210fbf5d77b10c748b3559ada0d4adad9d" + iex> EthereumJSONRPC.Zilliqa.AggregateQuorumCertificate.new(aqc_json, block_hash) + %EthereumJSONRPC.Zilliqa.AggregateQuorumCertificate{ + block_hash: "0x9c8a047e40ea975cb14c5ccff232a2210fbf5d77b10c748b3559ada0d4adad9d", + view: 1137866, + signature: "0x820f591cd78b29a69ba25bc85c4327fa3b0adb61a73a4f0bd943b4ab0b97e061eae9ac032d19fbfab7efb89fac2454ab0b89fea83185c0dac749ff55b0e2c21535a2b712872491577728db868d11939461a6bfde0d94d238f46b643bbe19767e", + quorum_certificates: %EthereumJSONRPC.Zilliqa.NestedQuorumCertificates{ + signers: [1, 2, 3, 8], + items: [ + %EthereumJSONRPC.Zilliqa.QuorumCertificate{ + block_hash: "0x9c8a047e40ea975cb14c5ccff232a2210fbf5d77b10c748b3559ada0d4adad9d", + view: 1137863, + signature: "0xa78c7f3e07e1df963ddeda17a1e5afd97c7c8a6fc8e0616249c22a2a1cc91f8eef6073cab8ba22b50cc7b38090f1ad9109473d30f24d57858d1f28c6679b3c4deeb800e5572b5e15604596594d506d3103a44d8b707da581f1a4b82310aeecb6", + signers: [0, 1, 3, 8] + }, + %EthereumJSONRPC.Zilliqa.QuorumCertificate{ + block_hash: "0x9c8a047e40ea975cb14c5ccff232a2210fbf5d77b10c748b3559ada0d4adad9d", + view: 1137863, + signature: "0xa78c7f3e07e1df963ddeda17a1e5afd97c7c8a6fc8e0616249c22a2a1cc91f8eef6073cab8ba22b50cc7b38090f1ad9109473d30f24d57858d1f28c6679b3c4deeb800e5572b5e15604596594d506d3103a44d8b707da581f1a4b82310aeecb6", + signers: [0, 1, 3, 8] + }, + %EthereumJSONRPC.Zilliqa.QuorumCertificate{ + block_hash: "0x9c8a047e40ea975cb14c5ccff232a2210fbf5d77b10c748b3559ada0d4adad9d", + view: 1137863, + signature: "0xa78c7f3e07e1df963ddeda17a1e5afd97c7c8a6fc8e0616249c22a2a1cc91f8eef6073cab8ba22b50cc7b38090f1ad9109473d30f24d57858d1f28c6679b3c4deeb800e5572b5e15604596594d506d3103a44d8b707da581f1a4b82310aeecb6", + signers: [0, 1, 3, 8] + }, + %EthereumJSONRPC.Zilliqa.QuorumCertificate{ + block_hash: "0x9c8a047e40ea975cb14c5ccff232a2210fbf5d77b10c748b3559ada0d4adad9d", + view: 1137863, + signature: "0xa78c7f3e07e1df963ddeda17a1e5afd97c7c8a6fc8e0616249c22a2a1cc91f8eef6073cab8ba22b50cc7b38090f1ad9109473d30f24d57858d1f28c6679b3c4deeb800e5572b5e15604596594d506d3103a44d8b707da581f1a4b82310aeecb6", + signers: [0, 1, 3, 8] + } + ] + } + } + """ + @spec new(elixir(), EthereumJSONRPC.hash()) :: t() + def new( + %{ + "view" => view, + "signature" => signature, + "cosigned" => bit_vector, + "quorum_certificates" => quorum_certificates + }, + block_hash + ) do + %__MODULE__{ + block_hash: block_hash, + view: quantity_to_integer(view), + signature: signature, + quorum_certificates: + NestedQuorumCertificates.new( + quorum_certificates, + bit_vector, + block_hash + ) + } + end + + @doc """ + Converts `t:t/0` format to params used in `Explorer.Chain`. + + ## Examples + + iex> aggregate_quorum_certificate = %EthereumJSONRPC.Zilliqa.AggregateQuorumCertificate{ + ...> block_hash: "0x9c8a047e40ea975cb14c5ccff232a2210fbf5d77b10c748b3559ada0d4adad9d", + ...> view: 1137866, + ...> signature: "0x820f591cd78b29a69ba25bc85c4327fa3b0adb61a73a4f0bd943b4ab0b97e061eae9ac032d19fbfab7efb89fac2454ab0b89fea83185c0dac749ff55b0e2c21535a2b712872491577728db868d11939461a6bfde0d94d238f46b643bbe19767e", + ...> quorum_certificates: %EthereumJSONRPC.Zilliqa.NestedQuorumCertificates{ + ...> signers: [1, 2, 3, 8], + ...> items: [ + ...> %EthereumJSONRPC.Zilliqa.QuorumCertificate{ + ...> block_hash: "0x9c8a047e40ea975cb14c5ccff232a2210fbf5d77b10c748b3559ada0d4adad9d", + ...> view: 1137863, + ...> signature: "0xa78c7f3e07e1df963ddeda17a1e5afd97c7c8a6fc8e0616249c22a2a1cc91f8eef6073cab8ba22b50cc7b38090f1ad9109473d30f24d57858d1f28c6679b3c4deeb800e5572b5e15604596594d506d3103a44d8b707da581f1a4b82310aeecb6", + ...> signers: [0, 1, 3, 8] + ...> }, + ...> %EthereumJSONRPC.Zilliqa.QuorumCertificate{ + ...> block_hash: "0x9c8a047e40ea975cb14c5ccff232a2210fbf5d77b10c748b3559ada0d4adad9d", + ...> view: 1137863, + ...> signature: "0xa78c7f3e07e1df963ddeda17a1e5afd97c7c8a6fc8e0616249c22a2a1cc91f8eef6073cab8ba22b50cc7b38090f1ad9109473d30f24d57858d1f28c6679b3c4deeb800e5572b5e15604596594d506d3103a44d8b707da581f1a4b82310aeecb6", + ...> signers: [0, 1, 3, 8] + ...> }, + ...> %EthereumJSONRPC.Zilliqa.QuorumCertificate{ + ...> block_hash: "0x9c8a047e40ea975cb14c5ccff232a2210fbf5d77b10c748b3559ada0d4adad9d", + ...> view: 1137863, + ...> signature: "0xa78c7f3e07e1df963ddeda17a1e5afd97c7c8a6fc8e0616249c22a2a1cc91f8eef6073cab8ba22b50cc7b38090f1ad9109473d30f24d57858d1f28c6679b3c4deeb800e5572b5e15604596594d506d3103a44d8b707da581f1a4b82310aeecb6", + ...> signers: [0, 1, 3, 8] + ...> }, + ...> %EthereumJSONRPC.Zilliqa.QuorumCertificate{ + ...> block_hash: "0x9c8a047e40ea975cb14c5ccff232a2210fbf5d77b10c748b3559ada0d4adad9d", + ...> view: 1137863, + ...> signature: "0xa78c7f3e07e1df963ddeda17a1e5afd97c7c8a6fc8e0616249c22a2a1cc91f8eef6073cab8ba22b50cc7b38090f1ad9109473d30f24d57858d1f28c6679b3c4deeb800e5572b5e15604596594d506d3103a44d8b707da581f1a4b82310aeecb6", + ...> signers: [0, 1, 3, 8] + ...> } + ...> ] + ...> } + ...> } + iex> EthereumJSONRPC.Zilliqa.AggregateQuorumCertificate.to_params(aggregate_quorum_certificate) + %{ + signature: "0x820f591cd78b29a69ba25bc85c4327fa3b0adb61a73a4f0bd943b4ab0b97e061eae9ac032d19fbfab7efb89fac2454ab0b89fea83185c0dac749ff55b0e2c21535a2b712872491577728db868d11939461a6bfde0d94d238f46b643bbe19767e", + block_hash: "0x9c8a047e40ea975cb14c5ccff232a2210fbf5d77b10c748b3559ada0d4adad9d", + view: 1137866 + } + """ + @spec to_params(t()) :: params() + def to_params(%__MODULE__{ + block_hash: block_hash, + view: view, + signature: signature + }) do + %{ + block_hash: block_hash, + view: view, + signature: signature + } + end +end diff --git a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/zilliqa/helper.ex b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/zilliqa/helper.ex new file mode 100644 index 0000000000..24e5b5088a --- /dev/null +++ b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/zilliqa/helper.ex @@ -0,0 +1,111 @@ +defmodule EthereumJSONRPC.Zilliqa.Helper do + @moduledoc """ + Helper functions for processing consensus data. + """ + alias EthereumJSONRPC.Zilliqa + alias EthereumJSONRPC.{Block, Blocks} + + alias EthereumJSONRPC.Zilliqa.{ + AggregateQuorumCertificate, + NestedQuorumCertificates, + QuorumCertificate + } + + @initial_acc %{ + zilliqa_quorum_certificates_params: [], + zilliqa_aggregate_quorum_certificates_params: [], + zilliqa_nested_quorum_certificates_params: [] + } + + @type consensus_data_params :: %{ + zilliqa_quorum_certificates_params: [QuorumCertificate.params()], + zilliqa_aggregate_quorum_certificates_params: [AggregateQuorumCertificate.params()], + zilliqa_nested_quorum_certificates_params: [NestedQuorumCertificates.params()] + } + + @spec extend_blocks_struct(Blocks.t(), Blocks.elixir()) :: Blocks.t() + def extend_blocks_struct(%Blocks{} = module, elixir_blocks) do + consensus_data_fields = + Enum.reduce( + elixir_blocks, + @initial_acc, + &reduce_to_consensus_data/2 + ) + + Map.merge(module, consensus_data_fields) + end + + @doc """ + Converts a bit vector string to a list of indexes where the bit corresponding + to signing validator is 1. + + ## Examples + + iex> bit_vector_to_signers("[1, 0, 1, 0]") + [0, 2] + + iex> bit_vector_to_signers("[1, 1, 1, 1]") + [0, 1, 2, 3] + """ + @spec bit_vector_to_signers(Zilliqa.bit_vector()) :: Zilliqa.signers() + def bit_vector_to_signers(bit_vector_string) do + bit_vector_string + |> Jason.decode!() + |> Enum.with_index() + |> Enum.filter(fn {bit, _} -> bit == 1 end) + |> Enum.map(fn {_, index} -> index end) + end + + @spec reduce_to_consensus_data( + Block.elixir(), + consensus_data_params() + ) :: consensus_data_params() + defp reduce_to_consensus_data( + elixir_block, + %{ + zilliqa_quorum_certificates_params: quorum_certificates_params_acc, + zilliqa_aggregate_quorum_certificates_params: aggregate_quorum_certificates_params_acc, + zilliqa_nested_quorum_certificates_params: aggregate_nested_quorum_certificates_params_acc + } + ) do + quorum_certificates_map = + elixir_block + |> Block.elixir_to_zilliqa_quorum_certificate() + |> case do + nil -> + %{zilliqa_quorum_certificates_params: quorum_certificates_params_acc} + + quorum_certificate -> + quorum_certificate_params = QuorumCertificate.to_params(quorum_certificate) + %{zilliqa_quorum_certificates_params: [quorum_certificate_params | quorum_certificates_params_acc]} + end + + aggregated_quorum_certificate_map = + elixir_block + |> Block.elixir_to_zilliqa_aggregate_quorum_certificate() + |> case do + nil -> + %{ + zilliqa_aggregate_quorum_certificates_params: aggregate_quorum_certificates_params_acc, + zilliqa_nested_quorum_certificates_params: aggregate_nested_quorum_certificates_params_acc + } + + aggregate_quorum_certificates -> + aggregate_quorum_certificate_params = + AggregateQuorumCertificate.to_params(aggregate_quorum_certificates) + + aggregate_nested_quorum_certificates_params = + NestedQuorumCertificates.to_params(aggregate_quorum_certificates.quorum_certificates) + + %{ + zilliqa_aggregate_quorum_certificates_params: [ + aggregate_quorum_certificate_params | aggregate_quorum_certificates_params_acc + ], + zilliqa_nested_quorum_certificates_params: + aggregate_nested_quorum_certificates_params ++ aggregate_nested_quorum_certificates_params_acc + } + end + + Map.merge(quorum_certificates_map, aggregated_quorum_certificate_map) + end +end diff --git a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/zilliqa/nested_quorum_certificates.ex b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/zilliqa/nested_quorum_certificates.ex new file mode 100644 index 0000000000..1dfb5192eb --- /dev/null +++ b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/zilliqa/nested_quorum_certificates.ex @@ -0,0 +1,187 @@ +defmodule EthereumJSONRPC.Zilliqa.NestedQuorumCertificates do + @moduledoc """ + Represents a list of quorum certificates that were proposed by different + validators in the aggregate quorum certificate. + """ + + import EthereumJSONRPC.Zilliqa.Helper, + only: [bit_vector_to_signers: 1] + + alias EthereumJSONRPC.Zilliqa + alias EthereumJSONRPC.Zilliqa.QuorumCertificate + + defstruct [:signers, :items] + + @type elixir :: [QuorumCertificate.elixir()] + + @type t :: %__MODULE__{ + signers: [non_neg_integer()], + items: [QuorumCertificate.t()] + } + + @type params :: %{ + proposed_by_validator_index: Zilliqa.validator_index(), + block_hash: EthereumJSONRPC.hash(), + view: non_neg_integer(), + signature: EthereumJSONRPC.hash(), + signers: Zilliqa.signers() + } + + @doc """ + Decodes the JSON object returned by JSONRPC node into the `t:t/0` format. + + ## Examples + + iex> aqc_json = %{ + ...> "cosigned" => "[0, 1, 1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]", + ...> "quorum_certificates" => [ + ...> %{ + ...> "block_hash" => "0x4b8939a7fb0d7de4b288bafd4d5caa02f53abf3c1e348fca5038eebbf68248fa", + ...> "cosigned" => "[1, 1, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]", + ...> "signature" => "0xa78c7f3e07e1df963ddeda17a1e5afd97c7c8a6fc8e0616249c22a2a1cc91f8eef6073cab8ba22b50cc7b38090f1ad9109473d30f24d57858d1f28c6679b3c4deeb800e5572b5e15604596594d506d3103a44d8b707da581f1a4b82310aeecb6", + ...> "view" => "0x115cc7" + ...> }, + ...> %{ + ...> "block_hash" => "0x4b8939a7fb0d7de4b288bafd4d5caa02f53abf3c1e348fca5038eebbf68248fa", + ...> "cosigned" => "[1, 1, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]", + ...> "signature" => "0xa78c7f3e07e1df963ddeda17a1e5afd97c7c8a6fc8e0616249c22a2a1cc91f8eef6073cab8ba22b50cc7b38090f1ad9109473d30f24d57858d1f28c6679b3c4deeb800e5572b5e15604596594d506d3103a44d8b707da581f1a4b82310aeecb6", + ...> "view" => "0x115cc7" + ...> }, + ...> %{ + ...> "block_hash" => "0x4b8939a7fb0d7de4b288bafd4d5caa02f53abf3c1e348fca5038eebbf68248fa", + ...> "cosigned" => "[1, 1, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]", + ...> "signature" => "0xa78c7f3e07e1df963ddeda17a1e5afd97c7c8a6fc8e0616249c22a2a1cc91f8eef6073cab8ba22b50cc7b38090f1ad9109473d30f24d57858d1f28c6679b3c4deeb800e5572b5e15604596594d506d3103a44d8b707da581f1a4b82310aeecb6", + ...> "view" => "0x115cc7" + ...> }, + ...> %{ + ...> "block_hash" => "0x4b8939a7fb0d7de4b288bafd4d5caa02f53abf3c1e348fca5038eebbf68248fa", + ...> "cosigned" => "[1, 1, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]", + ...> "signature" => "0xa78c7f3e07e1df963ddeda17a1e5afd97c7c8a6fc8e0616249c22a2a1cc91f8eef6073cab8ba22b50cc7b38090f1ad9109473d30f24d57858d1f28c6679b3c4deeb800e5572b5e15604596594d506d3103a44d8b707da581f1a4b82310aeecb6", + ...> "view" => "0x115cc7" + ...> } + ...> ], + ...> "signature" => "0x820f591cd78b29a69ba25bc85c4327fa3b0adb61a73a4f0bd943b4ab0b97e061eae9ac032d19fbfab7efb89fac2454ab0b89fea83185c0dac749ff55b0e2c21535a2b712872491577728db868d11939461a6bfde0d94d238f46b643bbe19767e", + ...> "view" => "0x115cca" + ...> } + iex> quorum_certificates = aqc_json["quorum_certificates"] + iex> bit_vector = aqc_json["cosigned"] + iex> block_hash = "0x9c8a047e40ea975cb14c5ccff232a2210fbf5d77b10c748b3559ada0d4adad9d" + iex> EthereumJSONRPC.Zilliqa.NestedQuorumCertificates.new(quorum_certificates, bit_vector, block_hash) + %EthereumJSONRPC.Zilliqa.NestedQuorumCertificates{ + signers: [1, 2, 3, 8], + items: [ + %EthereumJSONRPC.Zilliqa.QuorumCertificate{ + block_hash: "0x9c8a047e40ea975cb14c5ccff232a2210fbf5d77b10c748b3559ada0d4adad9d", + view: 1137863, + signature: "0xa78c7f3e07e1df963ddeda17a1e5afd97c7c8a6fc8e0616249c22a2a1cc91f8eef6073cab8ba22b50cc7b38090f1ad9109473d30f24d57858d1f28c6679b3c4deeb800e5572b5e15604596594d506d3103a44d8b707da581f1a4b82310aeecb6", + signers: [0, 1, 3, 8] + }, + %EthereumJSONRPC.Zilliqa.QuorumCertificate{ + block_hash: "0x9c8a047e40ea975cb14c5ccff232a2210fbf5d77b10c748b3559ada0d4adad9d", + view: 1137863, + signature: "0xa78c7f3e07e1df963ddeda17a1e5afd97c7c8a6fc8e0616249c22a2a1cc91f8eef6073cab8ba22b50cc7b38090f1ad9109473d30f24d57858d1f28c6679b3c4deeb800e5572b5e15604596594d506d3103a44d8b707da581f1a4b82310aeecb6", + signers: [0, 1, 3, 8] + }, + %EthereumJSONRPC.Zilliqa.QuorumCertificate{ + block_hash: "0x9c8a047e40ea975cb14c5ccff232a2210fbf5d77b10c748b3559ada0d4adad9d", + view: 1137863, + signature: "0xa78c7f3e07e1df963ddeda17a1e5afd97c7c8a6fc8e0616249c22a2a1cc91f8eef6073cab8ba22b50cc7b38090f1ad9109473d30f24d57858d1f28c6679b3c4deeb800e5572b5e15604596594d506d3103a44d8b707da581f1a4b82310aeecb6", + signers: [0, 1, 3, 8] + }, + %EthereumJSONRPC.Zilliqa.QuorumCertificate{ + block_hash: "0x9c8a047e40ea975cb14c5ccff232a2210fbf5d77b10c748b3559ada0d4adad9d", + view: 1137863, + signature: "0xa78c7f3e07e1df963ddeda17a1e5afd97c7c8a6fc8e0616249c22a2a1cc91f8eef6073cab8ba22b50cc7b38090f1ad9109473d30f24d57858d1f28c6679b3c4deeb800e5572b5e15604596594d506d3103a44d8b707da581f1a4b82310aeecb6", + signers: [0, 1, 3, 8] + } + ] + } + """ + @spec new([QuorumCertificate.elixir()], Zilliqa.bit_vector(), EthereumJSONRPC.hash()) :: t() + def new(quorum_certificates, bit_vector, block_hash) do + signers = bit_vector_to_signers(bit_vector) + items = Enum.map(quorum_certificates, &QuorumCertificate.new(&1, block_hash)) + + %__MODULE__{ + signers: signers, + items: items + } + end + + @doc """ + Converts `t:t/0` format to params used in `Explorer.Chain`. + + ## Examples + + iex> nested_quorum_certificates = %EthereumJSONRPC.Zilliqa.NestedQuorumCertificates{ + ...> signers: [1, 2, 3, 8], + ...> items: [ + ...> %EthereumJSONRPC.Zilliqa.QuorumCertificate{ + ...> block_hash: "0x9c8a047e40ea975cb14c5ccff232a2210fbf5d77b10c748b3559ada0d4adad9d", + ...> view: 1137863, + ...> signature: "0xa78c7f3e07e1df963ddeda17a1e5afd97c7c8a6fc8e0616249c22a2a1cc91f8eef6073cab8ba22b50cc7b38090f1ad9109473d30f24d57858d1f28c6679b3c4deeb800e5572b5e15604596594d506d3103a44d8b707da581f1a4b82310aeecb6", + ...> signers: [0, 1, 3, 8] + ...> }, + ...> %EthereumJSONRPC.Zilliqa.QuorumCertificate{ + ...> block_hash: "0x9c8a047e40ea975cb14c5ccff232a2210fbf5d77b10c748b3559ada0d4adad9d", + ...> view: 1137863, + ...> signature: "0xa78c7f3e07e1df963ddeda17a1e5afd97c7c8a6fc8e0616249c22a2a1cc91f8eef6073cab8ba22b50cc7b38090f1ad9109473d30f24d57858d1f28c6679b3c4deeb800e5572b5e15604596594d506d3103a44d8b707da581f1a4b82310aeecb6", + ...> signers: [0, 1, 3, 8] + ...> }, + ...> %EthereumJSONRPC.Zilliqa.QuorumCertificate{ + ...> block_hash: "0x9c8a047e40ea975cb14c5ccff232a2210fbf5d77b10c748b3559ada0d4adad9d", + ...> view: 1137863, + ...> signature: "0xa78c7f3e07e1df963ddeda17a1e5afd97c7c8a6fc8e0616249c22a2a1cc91f8eef6073cab8ba22b50cc7b38090f1ad9109473d30f24d57858d1f28c6679b3c4deeb800e5572b5e15604596594d506d3103a44d8b707da581f1a4b82310aeecb6", + ...> signers: [0, 1, 3, 8] + ...> }, + ...> %EthereumJSONRPC.Zilliqa.QuorumCertificate{ + ...> block_hash: "0x9c8a047e40ea975cb14c5ccff232a2210fbf5d77b10c748b3559ada0d4adad9d", + ...> view: 1137863, + ...> signature: "0xa78c7f3e07e1df963ddeda17a1e5afd97c7c8a6fc8e0616249c22a2a1cc91f8eef6073cab8ba22b50cc7b38090f1ad9109473d30f24d57858d1f28c6679b3c4deeb800e5572b5e15604596594d506d3103a44d8b707da581f1a4b82310aeecb6", + ...> signers: [0, 1, 3, 8] + ...> } + ...> ] + ...> } + iex> EthereumJSONRPC.Zilliqa.NestedQuorumCertificates.to_params(nested_quorum_certificates) + [ + %{ + signature: "0xa78c7f3e07e1df963ddeda17a1e5afd97c7c8a6fc8e0616249c22a2a1cc91f8eef6073cab8ba22b50cc7b38090f1ad9109473d30f24d57858d1f28c6679b3c4deeb800e5572b5e15604596594d506d3103a44d8b707da581f1a4b82310aeecb6", + block_hash: "0x9c8a047e40ea975cb14c5ccff232a2210fbf5d77b10c748b3559ada0d4adad9d", + view: 1137863, + signers: [0, 1, 3, 8], + proposed_by_validator_index: 1 + }, + %{ + signature: "0xa78c7f3e07e1df963ddeda17a1e5afd97c7c8a6fc8e0616249c22a2a1cc91f8eef6073cab8ba22b50cc7b38090f1ad9109473d30f24d57858d1f28c6679b3c4deeb800e5572b5e15604596594d506d3103a44d8b707da581f1a4b82310aeecb6", + block_hash: "0x9c8a047e40ea975cb14c5ccff232a2210fbf5d77b10c748b3559ada0d4adad9d", + view: 1137863, + signers: [0, 1, 3, 8], + proposed_by_validator_index: 2 + }, + %{ + signature: "0xa78c7f3e07e1df963ddeda17a1e5afd97c7c8a6fc8e0616249c22a2a1cc91f8eef6073cab8ba22b50cc7b38090f1ad9109473d30f24d57858d1f28c6679b3c4deeb800e5572b5e15604596594d506d3103a44d8b707da581f1a4b82310aeecb6", + block_hash: "0x9c8a047e40ea975cb14c5ccff232a2210fbf5d77b10c748b3559ada0d4adad9d", + view: 1137863, + signers: [0, 1, 3, 8], + proposed_by_validator_index: 3 + }, + %{ + signature: "0xa78c7f3e07e1df963ddeda17a1e5afd97c7c8a6fc8e0616249c22a2a1cc91f8eef6073cab8ba22b50cc7b38090f1ad9109473d30f24d57858d1f28c6679b3c4deeb800e5572b5e15604596594d506d3103a44d8b707da581f1a4b82310aeecb6", + block_hash: "0x9c8a047e40ea975cb14c5ccff232a2210fbf5d77b10c748b3559ada0d4adad9d", + view: 1137863, + signers: [0, 1, 3, 8], + proposed_by_validator_index: 8 + } + ] + """ + @spec to_params(t()) :: [params()] + def to_params(%__MODULE__{signers: signers, items: items}) do + signers + |> Enum.zip(items) + |> Enum.map(fn {validator_index, cert} -> + cert + |> QuorumCertificate.to_params() + |> Map.put(:proposed_by_validator_index, validator_index) + end) + end +end diff --git a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/zilliqa/quorum_certificate.ex b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/zilliqa/quorum_certificate.ex new file mode 100644 index 0000000000..2d85233064 --- /dev/null +++ b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/zilliqa/quorum_certificate.ex @@ -0,0 +1,100 @@ +defmodule EthereumJSONRPC.Zilliqa.QuorumCertificate do + @moduledoc """ + Represents a quorum certificate associated with the block. + """ + import EthereumJSONRPC, only: [quantity_to_integer: 1] + + import EthereumJSONRPC.Zilliqa.Helper, + only: [bit_vector_to_signers: 1] + + @type elixir :: %{ + String.t() => EthereumJSONRPC.quantity() | EthereumJSONRPC.Zilliqa.bit_vector() | EthereumJSONRPC.hash() + } + + @type t :: %__MODULE__{ + block_hash: EthereumJSONRPC.hash(), + view: non_neg_integer(), + signature: EthereumJSONRPC.hash(), + signers: [non_neg_integer()] + } + + @type params :: %{ + block_hash: EthereumJSONRPC.hash(), + view: non_neg_integer(), + signature: EthereumJSONRPC.hash(), + signers: [non_neg_integer()] + } + + defstruct [:block_hash, :view, :signature, :signers] + + @doc """ + Decodes the JSON object returned by JSONRPC node into the `t:t/0` format. + + ## Examples + + iex> qc_json = %{ + ...> "block_hash" => "0x4b8939a7fb0d7de4b288bafd4d5caa02f53abf3c1e348fca5038eebbf68248fa", + ...> "cosigned" => "[1, 1, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]", + ...> "signature" => "0xa78c7f3e07e1df963ddeda17a1e5afd97c7c8a6fc8e0616249c22a2a1cc91f8eef6073cab8ba22b50cc7b38090f1ad9109473d30f24d57858d1f28c6679b3c4deeb800e5572b5e15604596594d506d3103a44d8b707da581f1a4b82310aeecb6", + ...> "view" => "0x115cc7" + ...> } + iex> block_hash = "0x9c8a047e40ea975cb14c5ccff232a2210fbf5d77b10c748b3559ada0d4adad9d" + iex> EthereumJSONRPC.Zilliqa.QuorumCertificate.new(qc_json, block_hash) + %EthereumJSONRPC.Zilliqa.QuorumCertificate{ + block_hash: "0x9c8a047e40ea975cb14c5ccff232a2210fbf5d77b10c748b3559ada0d4adad9d", + view: 1137863, + signature: "0xa78c7f3e07e1df963ddeda17a1e5afd97c7c8a6fc8e0616249c22a2a1cc91f8eef6073cab8ba22b50cc7b38090f1ad9109473d30f24d57858d1f28c6679b3c4deeb800e5572b5e15604596594d506d3103a44d8b707da581f1a4b82310aeecb6", + signers: [0, 1, 3, 8] + } + """ + @spec new(elixir(), EthereumJSONRPC.hash()) :: t() + def new( + %{ + "view" => view, + "cosigned" => bit_vector, + "signature" => signature + }, + block_hash + ) do + %__MODULE__{ + block_hash: block_hash, + view: quantity_to_integer(view), + signature: signature, + signers: bit_vector_to_signers(bit_vector) + } + end + + @doc """ + Converts `t:t/0` format to params used in `Explorer.Chain`. + + ## Examples + + iex> qc = %EthereumJSONRPC.Zilliqa.QuorumCertificate{ + ...> block_hash: "0x9c8a047e40ea975cb14c5ccff232a2210fbf5d77b10c748b3559ada0d4adad9d", + ...> view: 1137863, + ...> signature: "0xa78c7f3e07e1df963ddeda17a1e5afd97c7c8a6fc8e0616249c22a2a1cc91f8eef6073cab8ba22b50cc7b38090f1ad9109473d30f24d57858d1f28c6679b3c4deeb800e5572b5e15604596594d506d3103a44d8b707da581f1a4b82310aeecb6", + ...> signers: [0, 1, 3, 8] + ...> } + iex> EthereumJSONRPC.Zilliqa.QuorumCertificate.to_params(qc) + %{ + block_hash: "0x9c8a047e40ea975cb14c5ccff232a2210fbf5d77b10c748b3559ada0d4adad9d", + view: 1137863, + signature: "0xa78c7f3e07e1df963ddeda17a1e5afd97c7c8a6fc8e0616249c22a2a1cc91f8eef6073cab8ba22b50cc7b38090f1ad9109473d30f24d57858d1f28c6679b3c4deeb800e5572b5e15604596594d506d3103a44d8b707da581f1a4b82310aeecb6", + signers: [0, 1, 3, 8] + } + """ + @spec to_params(t()) :: params() + def to_params(%__MODULE__{ + block_hash: block_hash, + view: view, + signature: signature, + signers: signers + }) do + %{ + block_hash: block_hash, + view: view, + signature: signature, + signers: signers + } + end +end diff --git a/apps/ethereum_jsonrpc/mix.exs b/apps/ethereum_jsonrpc/mix.exs index d965cdfe33..ddf0dd9705 100644 --- a/apps/ethereum_jsonrpc/mix.exs +++ b/apps/ethereum_jsonrpc/mix.exs @@ -1,4 +1,4 @@ -defmodule EthereumJsonrpc.MixProject do +defmodule EthereumJSONRPC.MixProject do use Mix.Project def project do diff --git a/apps/ethereum_jsonrpc/test/ethereum_jsonrpc/block_test.exs b/apps/ethereum_jsonrpc/test/ethereum_jsonrpc/block_test.exs index 7628b54da2..47f01c803b 100644 --- a/apps/ethereum_jsonrpc/test/ethereum_jsonrpc/block_test.exs +++ b/apps/ethereum_jsonrpc/test/ethereum_jsonrpc/block_test.exs @@ -85,6 +85,10 @@ defmodule EthereumJSONRPC.BlockTest do l1_block_number: nil } + :zilliqa -> + defp chain_type_fields, + do: %{zilliqa_view: nil} + _ -> defp chain_type_fields, do: %{} end diff --git a/apps/ethereum_jsonrpc/test/ethereum_jsonrpc/zilliqa_test.exs b/apps/ethereum_jsonrpc/test/ethereum_jsonrpc/zilliqa_test.exs new file mode 100644 index 0000000000..96d08c9101 --- /dev/null +++ b/apps/ethereum_jsonrpc/test/ethereum_jsonrpc/zilliqa_test.exs @@ -0,0 +1,7 @@ +defmodule EthereumJSONRPC.ZilliqaTest do + use ExUnit.Case, async: true + + doctest EthereumJSONRPC.Zilliqa.AggregateQuorumCertificate + doctest EthereumJSONRPC.Zilliqa.NestedQuorumCertificates + doctest EthereumJSONRPC.Zilliqa.QuorumCertificate +end diff --git a/apps/explorer/config/dev.exs b/apps/explorer/config/dev.exs index 7202a7f3ca..b95b70556a 100644 --- a/apps/explorer/config/dev.exs +++ b/apps/explorer/config/dev.exs @@ -5,51 +5,35 @@ config :explorer, Explorer.Repo, timeout: :timer.seconds(80), migration_lock: nil -# Configure API database -config :explorer, Explorer.Repo.Replica1, timeout: :timer.seconds(80) - -# Configure Account database -config :explorer, Explorer.Repo.Account, timeout: :timer.seconds(80) - -# Configure Optimism database -config :explorer, Explorer.Repo.Optimism, timeout: :timer.seconds(80) - -# Configure Polygon Edge database -config :explorer, Explorer.Repo.PolygonEdge, timeout: :timer.seconds(80) - -# Configure Polygon zkEVM database -config :explorer, Explorer.Repo.PolygonZkevm, timeout: :timer.seconds(80) - -# Configure Scroll database -config :explorer, Explorer.Repo.Scroll, timeout: :timer.seconds(80) - -# Configure ZkSync database -config :explorer, Explorer.Repo.ZkSync, timeout: :timer.seconds(80) - -config :explorer, Explorer.Repo.Celo, timeout: :timer.seconds(80) - -config :explorer, Explorer.Repo.RSK, timeout: :timer.seconds(80) - -config :explorer, Explorer.Repo.Shibarium, timeout: :timer.seconds(80) - -config :explorer, Explorer.Repo.Suave, timeout: :timer.seconds(80) - -config :explorer, Explorer.Repo.Beacon, timeout: :timer.seconds(80) - -# Configure Arbitrum database -config :explorer, Explorer.Repo.Arbitrum, timeout: :timer.seconds(80) - -config :explorer, Explorer.Repo.BridgedTokens, timeout: :timer.seconds(80) - -config :explorer, Explorer.Repo.Filecoin, timeout: :timer.seconds(80) - -config :explorer, Explorer.Repo.Stability, timeout: :timer.seconds(80) - -config :explorer, Explorer.Repo.Mud, timeout: :timer.seconds(80) - -config :explorer, Explorer.Repo.ShrunkInternalTransactions, timeout: :timer.seconds(80) - -config :explorer, Explorer.Repo.Blackfort, timeout: :timer.seconds(80) +for repo <- [ + # Configure API database + Explorer.Repo.Replica1, + + # Feature dependent repos + Explorer.Repo.Account, + Explorer.Repo.BridgedTokens, + Explorer.Repo.ShrunkInternalTransactions, + + # Chain-type dependent repos + Explorer.Repo.Arbitrum, + Explorer.Repo.Beacon, + Explorer.Repo.Blackfort, + Explorer.Repo.Celo, + Explorer.Repo.Filecoin, + Explorer.Repo.Mud, + Explorer.Repo.Optimism, + Explorer.Repo.PolygonEdge, + Explorer.Repo.PolygonZkevm, + Explorer.Repo.RSK, + Explorer.Repo.Scroll, + Explorer.Repo.Shibarium, + Explorer.Repo.Stability, + Explorer.Repo.Suave, + Explorer.Repo.Zilliqa, + Explorer.Repo.ZkSync + ] do + config :explorer, repo, timeout: :timer.seconds(80) +end config :explorer, Explorer.Tracer, env: "dev", disabled?: true diff --git a/apps/explorer/config/prod.exs b/apps/explorer/config/prod.exs index 0f1f30cd9f..89f52c6ad9 100644 --- a/apps/explorer/config/prod.exs +++ b/apps/explorer/config/prod.exs @@ -7,102 +7,38 @@ config :explorer, Explorer.Repo, migration_lock: nil, ssl_opts: [verify: :verify_none] -# Configures API the database -config :explorer, Explorer.Repo.Replica1, - prepare: :unnamed, - timeout: :timer.seconds(60), - ssl_opts: [verify: :verify_none] - -# Configures Account database -config :explorer, Explorer.Repo.Account, - prepare: :unnamed, - timeout: :timer.seconds(60), - ssl_opts: [verify: :verify_none] - -config :explorer, Explorer.Repo.Optimism, - prepare: :unnamed, - timeout: :timer.seconds(60), - ssl_opts: [verify: :verify_none] - -config :explorer, Explorer.Repo.PolygonEdge, - prepare: :unnamed, - timeout: :timer.seconds(60), - ssl_opts: [verify: :verify_none] - -config :explorer, Explorer.Repo.PolygonZkevm, - prepare: :unnamed, - timeout: :timer.seconds(60), - ssl_opts: [verify: :verify_none] - -config :explorer, Explorer.Repo.Scroll, - prepare: :unnamed, - timeout: :timer.seconds(60), - ssl_opts: [verify: :verify_none] - -config :explorer, Explorer.Repo.ZkSync, - prepare: :unnamed, - timeout: :timer.seconds(60), - ssl_opts: [verify: :verify_none] - -config :explorer, Explorer.Repo.Celo, - prepare: :unnamed, - timeout: :timer.seconds(60), - ssl_opts: [verify: :verify_none] - -config :explorer, Explorer.Repo.RSK, - prepare: :unnamed, - timeout: :timer.seconds(60), - ssl_opts: [verify: :verify_none] - -config :explorer, Explorer.Repo.Shibarium, - prepare: :unnamed, - timeout: :timer.seconds(60), - ssl_opts: [verify: :verify_none] - -config :explorer, Explorer.Repo.Suave, - prepare: :unnamed, - timeout: :timer.seconds(60), - ssl_opts: [verify: :verify_none] - -config :explorer, Explorer.Repo.Beacon, - prepare: :unnamed, - timeout: :timer.seconds(60), - ssl_opts: [verify: :verify_none] - -config :explorer, Explorer.Repo.Arbitrum, - prepare: :unnamed, - timeout: :timer.seconds(60), - ssl_opts: [verify: :verify_none] - -config :explorer, Explorer.Repo.BridgedTokens, - prepare: :unnamed, - timeout: :timer.seconds(60), - ssl_opts: [verify: :verify_none] - -config :explorer, Explorer.Repo.Filecoin, - prepare: :unnamed, - timeout: :timer.seconds(60), - ssl_opts: [verify: :verify_none] - -config :explorer, Explorer.Repo.Stability, - prepare: :unnamed, - timeout: :timer.seconds(60), - ssl_opts: [verify: :verify_none] - -config :explorer, Explorer.Repo.Mud, - prepare: :unnamed, - timeout: :timer.seconds(60), - ssl_opts: [verify: :verify_none] - -config :explorer, Explorer.Repo.ShrunkInternalTransactions, - prepare: :unnamed, - timeout: :timer.seconds(60), - ssl_opts: [verify: :verify_none] - -config :explorer, Explorer.Repo.Blackfort, - prepare: :unnamed, - timeout: :timer.seconds(60), - ssl_opts: [verify: :verify_none] +for repo <- [ + # Configures API the database + Explorer.Repo.Replica1, + + # Feature dependent repos + Explorer.Repo.Account, + Explorer.Repo.BridgedTokens, + Explorer.Repo.ShrunkInternalTransactions, + + # Chain-type dependent repos + Explorer.Repo.Arbitrum, + Explorer.Repo.Beacon, + Explorer.Repo.Blackfort, + Explorer.Repo.Celo, + Explorer.Repo.Filecoin, + Explorer.Repo.Mud, + Explorer.Repo.Optimism, + Explorer.Repo.PolygonEdge, + Explorer.Repo.PolygonZkevm, + Explorer.Repo.RSK, + Explorer.Repo.Scroll, + Explorer.Repo.Shibarium, + Explorer.Repo.Stability, + Explorer.Repo.Suave, + Explorer.Repo.Zilliqa, + Explorer.Repo.ZkSync + ] do + config :explorer, repo, + prepare: :unnamed, + timeout: :timer.seconds(60), + ssl_opts: [verify: :verify_none] +end config :explorer, Explorer.Tracer, env: "production", disabled?: true diff --git a/apps/explorer/config/test.exs b/apps/explorer/config/test.exs index f0f047be57..3ef8c7ab40 100644 --- a/apps/explorer/config/test.exs +++ b/apps/explorer/config/test.exs @@ -54,23 +54,24 @@ config :explorer, Explorer.Repo.Account, log: false for repo <- [ + Explorer.Repo.Arbitrum, Explorer.Repo.Beacon, + Explorer.Repo.Blackfort, + Explorer.Repo.BridgedTokens, + Explorer.Repo.Celo, + Explorer.Repo.Filecoin, + Explorer.Repo.Mud, Explorer.Repo.Optimism, Explorer.Repo.PolygonEdge, Explorer.Repo.PolygonZkevm, - Explorer.Repo.Scroll, - Explorer.Repo.ZkSync, - Explorer.Repo.Celo, Explorer.Repo.RSK, + Explorer.Repo.Scroll, Explorer.Repo.Shibarium, - Explorer.Repo.Suave, - Explorer.Repo.Arbitrum, - Explorer.Repo.BridgedTokens, - Explorer.Repo.Filecoin, - Explorer.Repo.Stability, - Explorer.Repo.Mud, Explorer.Repo.ShrunkInternalTransactions, - Explorer.Repo.Blackfort + Explorer.Repo.Stability, + Explorer.Repo.Suave, + Explorer.Repo.Zilliqa, + Explorer.Repo.ZkSync ] do config :explorer, repo, database: database, diff --git a/apps/explorer/lib/explorer/application.ex b/apps/explorer/lib/explorer/application.ex index 33aaed8792..cfa4f10dfc 100644 --- a/apps/explorer/lib/explorer/application.ex +++ b/apps/explorer/lib/explorer/application.ex @@ -166,22 +166,23 @@ defmodule Explorer.Application do defp repos_by_chain_type do if Mix.env() == :test do [ + Explorer.Repo.Arbitrum, Explorer.Repo.Beacon, + Explorer.Repo.Blackfort, + Explorer.Repo.BridgedTokens, + Explorer.Repo.Celo, + Explorer.Repo.Filecoin, Explorer.Repo.Optimism, Explorer.Repo.PolygonEdge, Explorer.Repo.PolygonZkevm, - Explorer.Repo.Scroll, - Explorer.Repo.ZkSync, - Explorer.Repo.Celo, Explorer.Repo.RSK, + Explorer.Repo.Scroll, Explorer.Repo.Shibarium, - Explorer.Repo.Suave, - Explorer.Repo.Arbitrum, - Explorer.Repo.BridgedTokens, - Explorer.Repo.Filecoin, - Explorer.Repo.Stability, Explorer.Repo.ShrunkInternalTransactions, - Explorer.Repo.Blackfort + Explorer.Repo.Stability, + Explorer.Repo.Suave, + Explorer.Repo.Zilliqa, + Explorer.Repo.ZkSync ] else [] diff --git a/apps/explorer/lib/explorer/chain/block.ex b/apps/explorer/lib/explorer/chain/block.ex index 6cc8e049d3..311242d57c 100644 --- a/apps/explorer/lib/explorer/chain/block.ex +++ b/apps/explorer/lib/explorer/chain/block.ex @@ -19,6 +19,8 @@ defmodule Explorer.Chain.Block.Schema do alias Explorer.Chain.Block.{Reward, SecondDegreeRelation} alias Explorer.Chain.Celo.EpochReward, as: CeloEpochReward alias Explorer.Chain.Optimism.TransactionBatch, as: OptimismTransactionBatch + alias Explorer.Chain.Zilliqa.AggregateQuorumCertificate, as: ZilliqaAggregateQuorumCertificate + alias Explorer.Chain.Zilliqa.QuorumCertificate, as: ZilliqaQuorumCertificate alias Explorer.Chain.ZkSync.BatchBlock, as: ZkSyncBatchBlock @chain_type_fields (case Application.compile_env(:explorer, :chain_type) do @@ -106,6 +108,24 @@ defmodule Explorer.Chain.Block.Schema do 2 ) + :zilliqa -> + elem( + quote do + field(:zilliqa_view, :integer) + + has_one(:zilliqa_quorum_certificate, ZilliqaQuorumCertificate, + foreign_key: :block_hash, + references: :hash + ) + + has_one(:zilliqa_aggregate_quorum_certificate, ZilliqaAggregateQuorumCertificate, + foreign_key: :block_hash, + references: :hash + ) + end, + 2 + ) + _ -> [] end) @@ -183,6 +203,9 @@ defmodule Explorer.Chain.Block do :arbitrum -> ~w(send_count send_root l1_block_number)a + :zilliqa -> + ~w(zilliqa_view)a + _ -> ~w()a end) diff --git a/apps/explorer/lib/explorer/chain/import/runner/zilliqa/aggregate_quorum_certificates.ex b/apps/explorer/lib/explorer/chain/import/runner/zilliqa/aggregate_quorum_certificates.ex new file mode 100644 index 0000000000..42a2b00357 --- /dev/null +++ b/apps/explorer/lib/explorer/chain/import/runner/zilliqa/aggregate_quorum_certificates.ex @@ -0,0 +1,79 @@ +defmodule Explorer.Chain.Import.Runner.Zilliqa.AggregateQuorumCertificates do + @moduledoc """ + Bulk imports `t:Explorer.Chain.Zilliqa.AggregateQuorumCertificate.t/0`. + """ + + require Ecto.Query + + alias Ecto.{Changeset, Multi, Repo} + alias Explorer.Chain.Import + alias Explorer.Chain.Zilliqa.AggregateQuorumCertificate + alias Explorer.Prometheus.Instrumenter + + @behaviour Import.Runner + + # milliseconds + @timeout 60_000 + + @type imported :: [AggregateQuorumCertificate.t()] + + @impl Import.Runner + def ecto_schema_module, do: AggregateQuorumCertificate + + @impl Import.Runner + def option_key, do: :zilliqa_aggregate_quorum_certificates + + @impl Import.Runner + @spec imported_table_row() :: %{:value_description => binary(), :value_type => binary()} + def imported_table_row do + %{ + value_type: "[#{ecto_schema_module()}.t()]", + value_description: "List of `t:#{ecto_schema_module()}.t/0`s" + } + end + + @impl Import.Runner + @spec run(Multi.t(), list(), map()) :: Multi.t() + def run(multi, changes_list, %{timestamps: timestamps} = options) do + insert_options = + options + |> Map.get(option_key(), %{}) + |> Map.take(~w(on_conflict timeout)a) + |> Map.put_new(:timeout, @timeout) + |> Map.put(:timestamps, timestamps) + + Multi.run(multi, :insert_zilliqa_aggregate_quorum_certificates, fn repo, _ -> + Instrumenter.block_import_stage_runner( + fn -> insert(repo, changes_list, insert_options) end, + :block_referencing, + :zilliqa_aggregate_quorum_certificates, + :zilliqa_aggregate_quorum_certificates + ) + end) + end + + @impl Import.Runner + def timeout, do: @timeout + + @spec insert(Repo.t(), [map()], %{required(:timeout) => timeout(), required(:timestamps) => Import.timestamps()}) :: + {:ok, [AggregateQuorumCertificate.t()]} + | {:error, [Changeset.t()]} + def insert(repo, changes_list, %{timeout: timeout, timestamps: timestamps} = _options) when is_list(changes_list) do + # Enforce Zilliqa.AggregateQuorumCertificate ShareLocks order (see docs: sharelock.md) + ordered_changes_list = Enum.sort_by(changes_list, & &1.block_hash) + + {:ok, inserted} = + Import.insert_changes_list( + repo, + ordered_changes_list, + for: AggregateQuorumCertificate, + returning: true, + timeout: timeout, + timestamps: timestamps, + conflict_target: :block_hash, + on_conflict: :nothing + ) + + {:ok, inserted} + end +end diff --git a/apps/explorer/lib/explorer/chain/import/runner/zilliqa/nested_quorum_certificates.ex b/apps/explorer/lib/explorer/chain/import/runner/zilliqa/nested_quorum_certificates.ex new file mode 100644 index 0000000000..78cc049798 --- /dev/null +++ b/apps/explorer/lib/explorer/chain/import/runner/zilliqa/nested_quorum_certificates.ex @@ -0,0 +1,86 @@ +defmodule Explorer.Chain.Import.Runner.Zilliqa.NestedQuorumCertificates do + @moduledoc """ + Bulk imports `t:Explorer.Chain.Zilliqa.NestedQuorumCertificate.t/0`. + """ + + require Ecto.Query + + alias Ecto.{Changeset, Multi, Repo} + alias Explorer.Chain.Import + alias Explorer.Chain.Zilliqa.NestedQuorumCertificate + alias Explorer.Prometheus.Instrumenter + + @behaviour Import.Runner + + # milliseconds + @timeout 60_000 + + @type imported :: [NestedQuorumCertificate.t()] + + @impl Import.Runner + def ecto_schema_module, do: NestedQuorumCertificate + + @impl Import.Runner + def option_key, do: :zilliqa_nested_quorum_certificates + + @impl Import.Runner + @spec imported_table_row() :: %{:value_description => binary(), :value_type => binary()} + def imported_table_row do + %{ + value_type: "[#{ecto_schema_module()}.t()]", + value_description: "List of `t:#{ecto_schema_module()}.t/0`s" + } + end + + @impl Import.Runner + @spec run(Multi.t(), list(), map()) :: Multi.t() + def run(multi, changes_list, %{timestamps: timestamps} = options) do + insert_options = + options + |> Map.get(option_key(), %{}) + |> Map.take(~w(on_conflict timeout)a) + |> Map.put_new(:timeout, @timeout) + |> Map.put(:timestamps, timestamps) + + Multi.run(multi, :insert_zilliqa_nested_quorum_certificates, fn repo, _ -> + Instrumenter.block_import_stage_runner( + fn -> insert(repo, changes_list, insert_options) end, + :block_referencing, + :zilliqa_nested_quorum_certificates, + :zilliqa_nested_quorum_certificates + ) + end) + end + + @impl Import.Runner + def timeout, do: @timeout + + @spec insert(Repo.t(), [map()], %{required(:timeout) => timeout(), required(:timestamps) => Import.timestamps()}) :: + {:ok, [NestedQuorumCertificate.t()]} + | {:error, [Changeset.t()]} + def insert(repo, changes_list, %{timeout: timeout, timestamps: timestamps} = _options) when is_list(changes_list) do + # Enforce Zilliqa.NestedQuorumCertificate ShareLocks order (see docs: sharelock.md) + ordered_changes_list = + Enum.sort_by( + changes_list, + &{&1.block_hash, &1.proposed_by_validator_index} + ) + + {:ok, inserted} = + Import.insert_changes_list( + repo, + ordered_changes_list, + for: NestedQuorumCertificate, + returning: true, + timeout: timeout, + timestamps: timestamps, + conflict_target: [ + :block_hash, + :proposed_by_validator_index + ], + on_conflict: :nothing + ) + + {:ok, inserted} + end +end diff --git a/apps/explorer/lib/explorer/chain/import/runner/zilliqa/quorum_certificates.ex b/apps/explorer/lib/explorer/chain/import/runner/zilliqa/quorum_certificates.ex new file mode 100644 index 0000000000..e7b876910a --- /dev/null +++ b/apps/explorer/lib/explorer/chain/import/runner/zilliqa/quorum_certificates.ex @@ -0,0 +1,79 @@ +defmodule Explorer.Chain.Import.Runner.Zilliqa.QuorumCertificates do + @moduledoc """ + Bulk imports `t:Explorer.Chain.Zilliqa.QuorumCertificate.t/0`. + """ + + require Ecto.Query + + alias Ecto.{Changeset, Multi, Repo} + alias Explorer.Chain.Import + alias Explorer.Chain.Zilliqa.QuorumCertificate + alias Explorer.Prometheus.Instrumenter + + @behaviour Import.Runner + + # milliseconds + @timeout 60_000 + + @type imported :: [QuorumCertificate.t()] + + @impl Import.Runner + def ecto_schema_module, do: QuorumCertificate + + @impl Import.Runner + def option_key, do: :zilliqa_quorum_certificates + + @impl Import.Runner + @spec imported_table_row() :: %{:value_description => binary(), :value_type => binary()} + def imported_table_row do + %{ + value_type: "[#{ecto_schema_module()}.t()]", + value_description: "List of `t:#{ecto_schema_module()}.t/0`s" + } + end + + @impl Import.Runner + @spec run(Multi.t(), list(), map()) :: Multi.t() + def run(multi, changes_list, %{timestamps: timestamps} = options) do + insert_options = + options + |> Map.get(option_key(), %{}) + |> Map.take(~w(on_conflict timeout)a) + |> Map.put_new(:timeout, @timeout) + |> Map.put(:timestamps, timestamps) + + Multi.run(multi, :insert_zilliqa_quorum_certificates, fn repo, _ -> + Instrumenter.block_import_stage_runner( + fn -> insert(repo, changes_list, insert_options) end, + :block_referencing, + :zilliqa_quorum_certificates, + :zilliqa_quorum_certificates + ) + end) + end + + @impl Import.Runner + def timeout, do: @timeout + + @spec insert(Repo.t(), [map()], %{required(:timeout) => timeout(), required(:timestamps) => Import.timestamps()}) :: + {:ok, [QuorumCertificate.t()]} + | {:error, [Changeset.t()]} + def insert(repo, changes_list, %{timeout: timeout, timestamps: timestamps} = _options) when is_list(changes_list) do + # Enforce Zilliqa.QuorumCertificate ShareLocks order (see docs: sharelock.md) + ordered_changes_list = Enum.sort_by(changes_list, & &1.block_hash) + + {:ok, inserted} = + Import.insert_changes_list( + repo, + ordered_changes_list, + for: QuorumCertificate, + returning: true, + timeout: timeout, + timestamps: timestamps, + conflict_target: :block_hash, + on_conflict: :nothing + ) + + {:ok, inserted} + end +end diff --git a/apps/explorer/lib/explorer/chain/import/stage/chain_type_specific.ex b/apps/explorer/lib/explorer/chain/import/stage/chain_type_specific.ex index f897810598..567bd3f6b1 100644 --- a/apps/explorer/lib/explorer/chain/import/stage/chain_type_specific.ex +++ b/apps/explorer/lib/explorer/chain/import/stage/chain_type_specific.ex @@ -62,6 +62,11 @@ defmodule Explorer.Chain.Import.Stage.ChainTypeSpecific do Runner.Celo.ValidatorGroupVotes, Runner.Celo.ElectionRewards, Runner.Celo.EpochRewards + ], + zilliqa: [ + Runner.Zilliqa.AggregateQuorumCertificates, + Runner.Zilliqa.NestedQuorumCertificates, + Runner.Zilliqa.QuorumCertificates ] } diff --git a/apps/explorer/lib/explorer/chain/zilliqa/aggregate_quorum_certificate.ex b/apps/explorer/lib/explorer/chain/zilliqa/aggregate_quorum_certificate.ex new file mode 100644 index 0000000000..f26936009f --- /dev/null +++ b/apps/explorer/lib/explorer/chain/zilliqa/aggregate_quorum_certificate.ex @@ -0,0 +1,64 @@ +defmodule Explorer.Chain.Zilliqa.AggregateQuorumCertificate do + @moduledoc """ + A stored representation of a Zilliqa aggregate quorum certificate in the + context of PBFT consensus. + + In PBFT (Practical Byzantine Fault Tolerance) consensus, an aggregate quorum + certificate combines multiple quorum certificates into one, providing proof + that a block has been approved across multiple consensus rounds or by multiple + subsets of validators. It includes aggregated signatures and references to + nested quorum certificates. + + Changes in the schema should be reflected in the bulk import module: + - `Explorer.Chain.Import.Runner.Zilliqa.AggregateQuorumCertificate` + """ + use Explorer.Schema + + alias Explorer.Chain.{Block, Hash} + alias Explorer.Chain.Zilliqa.Hash.Signature, as: SignatureHash + alias Explorer.Chain.Zilliqa.NestedQuorumCertificate + + @required_attrs ~w(block_hash view signature)a + + @typedoc """ + * `view` - the view number associated with the quorum certificate, indicating + the consensus round. + * `signature` - the aggregated BLS (Boneh–Lynn–Shacham) signature representing + the validators' agreement. + * `block_hash` - the hash of the block associated with this aggregate quorum + certificate. + * `nested_quorum_certificates` - the list of nested quorum certificates that + are part of this aggregate. + """ + @primary_key false + typed_schema "zilliqa_aggregate_quorum_certificates" do + field(:view, :integer) + field(:signature, SignatureHash) + + belongs_to(:block, Block, + foreign_key: :block_hash, + primary_key: true, + references: :hash, + type: Hash.Full, + null: false + ) + + has_many( + :nested_quorum_certificates, + NestedQuorumCertificate, + foreign_key: :block_hash, + references: :block_hash + ) + + timestamps() + end + + @spec changeset(Ecto.Schema.t(), map()) :: Ecto.Changeset.t() + def changeset(%__MODULE__{} = cert, attrs) do + cert + |> cast(attrs, @required_attrs) + |> validate_required(@required_attrs) + |> foreign_key_constraint(:block_hash) + |> unique_constraint(:block_hash, name: :aggregate_quorum_certificates_pkey) + end +end diff --git a/apps/explorer/lib/explorer/chain/zilliqa/hash/signature.ex b/apps/explorer/lib/explorer/chain/zilliqa/hash/signature.ex new file mode 100644 index 0000000000..44c4253f99 --- /dev/null +++ b/apps/explorer/lib/explorer/chain/zilliqa/hash/signature.ex @@ -0,0 +1,60 @@ +defmodule Explorer.Chain.Zilliqa.Hash.Signature do + @moduledoc """ + A 96-byte BLS signature of the supermajority of the validators. + """ + + alias Explorer.Chain.Hash + + use Ecto.Type + @behaviour Hash + + @byte_count 96 + + @typedoc """ + A #{@byte_count}-byte BLS signature hash of the + `t:Explorer.Chain.Zilliqa.QuorumCertificate.t/0` or + `t:Explorer.Chain.Zilliqa.AggregateQuorumCertificate.t/0` or + `t:Explorer.Chain.Zilliqa.NestedQuorumCertificate.t/0`. + """ + @type t :: %Hash{byte_count: unquote(@byte_count), bytes: <<_::unquote(@byte_count * Hash.bits_per_byte())>>} + + @doc """ + Casts a term to a `t`. + """ + @impl Ecto.Type + @spec cast(term()) :: {:ok, t()} | :error + def cast(term) do + Hash.cast(__MODULE__, term) + end + + @doc """ + Dumps a `t` to a binary. + """ + @impl Ecto.Type + @spec dump(term()) :: {:ok, binary} | :error + def dump(term) do + Hash.dump(__MODULE__, term) + end + + @doc """ + Loads a binary to a `t`. + """ + @impl Ecto.Type + @spec load(term()) :: {:ok, t()} | :error + def load(term) do + Hash.load(__MODULE__, term) + end + + @doc """ + Returns the type of the `t`. + """ + @impl Ecto.Type + @spec type() :: :binary + def type, do: :binary + + @doc """ + Returns the byte count of the `t`. + """ + @impl Hash + def byte_count, do: @byte_count +end diff --git a/apps/explorer/lib/explorer/chain/zilliqa/nested_quorum_certificate.ex b/apps/explorer/lib/explorer/chain/zilliqa/nested_quorum_certificate.ex new file mode 100644 index 0000000000..8edd55a148 --- /dev/null +++ b/apps/explorer/lib/explorer/chain/zilliqa/nested_quorum_certificate.ex @@ -0,0 +1,64 @@ +defmodule Explorer.Chain.Zilliqa.NestedQuorumCertificate do + @moduledoc """ + A stored representation of a nested quorum certificate in Zilliqa's PBFT + consensus. + + In Zilliqa's PBFT (Practical Byzantine Fault Tolerance) consensus, an + aggregate quorum certificate may include multiple nested quorum certificates. + Each nested quorum certificate represents a quorum certificate proposed by a + specific validator and contains its own aggregated signatures and participant + information. + + Changes in the schema should be reflected in the bulk import module: + - `Explorer.Chain.Import.Runner.Zilliqa.AggregateQuorumCertificate` + """ + use Explorer.Schema + + alias Explorer.Chain.Hash + alias Explorer.Chain.Zilliqa.AggregateQuorumCertificate + alias Explorer.Chain.Zilliqa.Hash.Signature, as: SignatureHash + + @required_attrs ~w(block_hash proposed_by_validator_index view signature signers)a + + @typedoc """ + * `proposed_by_validator_index` - the index of the validator who proposed this + nested quorum certificate. + * `view` - the view number associated with the quorum certificate, indicating + the consensus round. + * `signature` - the aggregated BLS (Boneh–Lynn–Shacham) signature representing + the validators' agreement. + * `signers` - the array of integers representing the indices of validators who + participated in the quorum (as indicated by the `cosigned` bit vector). + * `block_hash` - the hash of the block associated with the aggregate quorum + certificate to which this nested quorum certificate belongs. + """ + @primary_key false + typed_schema "zilliqa_nested_quorum_certificates" do + field(:proposed_by_validator_index, :integer, primary_key: true) + field(:view, :integer) + field(:signature, SignatureHash) + field(:signers, {:array, :integer}) + + belongs_to(:aggregate_quorum_certificate, AggregateQuorumCertificate, + foreign_key: :block_hash, + references: :block_hash, + primary_key: true, + type: Hash.Full, + null: false + ) + + timestamps() + end + + @spec changeset(Ecto.Schema.t(), map()) :: Ecto.Changeset.t() + def changeset(%__MODULE__{} = cert, attrs) do + cert + |> cast(attrs, @required_attrs) + |> validate_required(@required_attrs) + |> foreign_key_constraint(:block_hash) + |> unique_constraint( + [:proposed_by_validator_index, :block_hash], + name: :nested_quorum_certificates_pkey + ) + end +end diff --git a/apps/explorer/lib/explorer/chain/zilliqa/quorum_certificate.ex b/apps/explorer/lib/explorer/chain/zilliqa/quorum_certificate.ex new file mode 100644 index 0000000000..61235bae26 --- /dev/null +++ b/apps/explorer/lib/explorer/chain/zilliqa/quorum_certificate.ex @@ -0,0 +1,56 @@ +defmodule Explorer.Chain.Zilliqa.QuorumCertificate do + @moduledoc """ + A stored representation of a Zilliqa quorum certificate in the context of PBFT + consensus. + + In PBFT (Practical Byzantine Fault Tolerance) consensus, a quorum certificate + is a data structure that serves as proof that a block has been approved by a + supermajority of validators. It includes aggregated signatures and a bitmap + indicating which validators participated in the consensus. + + Changes in the schema should be reflected in the bulk import module: + - `Explorer.Chain.Import.Runner.Zilliqa.AggregateQuorumCertificate` + """ + use Explorer.Schema + + alias Explorer.Chain.{Block, Hash} + alias Explorer.Chain.Zilliqa.Hash.Signature, as: SignatureHash + + @required_attrs ~w(block_hash view signature signers)a + + @typedoc """ + * `view` - the view number associated with the quorum certificate, indicating + the consensus round. + * `signature` - the aggregated BLS (Boneh–Lynn–Shacham) signature representing + the validators' agreement. + * `signers` - the array of integers representing the indices of validators who + participated in the quorum (as indicated by the `cosigned` bit vector). + * `block_hash` - the hash of the block associated with this quorum + certificate. + """ + @primary_key false + typed_schema "zilliqa_quorum_certificates" do + field(:view, :integer) + field(:signature, SignatureHash) + field(:signers, {:array, :integer}) + + belongs_to(:block, Block, + foreign_key: :block_hash, + primary_key: true, + references: :hash, + type: Hash.Full, + null: false + ) + + timestamps() + end + + @spec changeset(Ecto.Schema.t(), map()) :: Ecto.Schema.t() + def changeset(%__MODULE__{} = cert, attrs) do + cert + |> cast(attrs, @required_attrs) + |> validate_required(@required_attrs) + |> foreign_key_constraint(:block_hash) + |> unique_constraint(:block_hash, name: :quorum_certificates_pkey) + end +end diff --git a/apps/explorer/lib/explorer/repo.ex b/apps/explorer/lib/explorer/repo.ex index 5f1ff6e174..7b159e2635 100644 --- a/apps/explorer/lib/explorer/repo.ex +++ b/apps/explorer/lib/explorer/repo.ex @@ -127,183 +127,38 @@ defmodule Explorer.Repo do end end - defmodule Account do - use Ecto.Repo, - otp_app: :explorer, - adapter: Ecto.Adapters.Postgres - - def init(_, opts) do - ConfigHelper.init_repo_module(__MODULE__, opts) - end - end - - defmodule Optimism do - use Ecto.Repo, - otp_app: :explorer, - adapter: Ecto.Adapters.Postgres - - def init(_, opts) do - ConfigHelper.init_repo_module(__MODULE__, opts) - end - end - - defmodule PolygonEdge do - use Ecto.Repo, - otp_app: :explorer, - adapter: Ecto.Adapters.Postgres - - def init(_, opts) do - ConfigHelper.init_repo_module(__MODULE__, opts) - end - end - - defmodule PolygonZkevm do - use Ecto.Repo, - otp_app: :explorer, - adapter: Ecto.Adapters.Postgres - - def init(_, opts) do - ConfigHelper.init_repo_module(__MODULE__, opts) - end - end - - defmodule Scroll do - use Ecto.Repo, - otp_app: :explorer, - adapter: Ecto.Adapters.Postgres - - def init(_, opts) do - ConfigHelper.init_repo_module(__MODULE__, opts) - end - end - - defmodule ZkSync do - use Ecto.Repo, - otp_app: :explorer, - adapter: Ecto.Adapters.Postgres - - def init(_, opts) do - ConfigHelper.init_repo_module(__MODULE__, opts) - end - end - - defmodule Celo do - use Ecto.Repo, - otp_app: :explorer, - adapter: Ecto.Adapters.Postgres - - def init(_, opts) do - ConfigHelper.init_repo_module(__MODULE__, opts) - end - end - - defmodule RSK do - use Ecto.Repo, - otp_app: :explorer, - adapter: Ecto.Adapters.Postgres - - def init(_, opts) do - ConfigHelper.init_repo_module(__MODULE__, opts) - end - end - - defmodule Shibarium do - use Ecto.Repo, - otp_app: :explorer, - adapter: Ecto.Adapters.Postgres - - def init(_, opts) do - ConfigHelper.init_repo_module(__MODULE__, opts) - end - end - - defmodule Suave do - use Ecto.Repo, - otp_app: :explorer, - adapter: Ecto.Adapters.Postgres - - def init(_, opts) do - ConfigHelper.init_repo_module(__MODULE__, opts) - end - end - - defmodule Beacon do - use Ecto.Repo, - otp_app: :explorer, - adapter: Ecto.Adapters.Postgres - - def init(_, opts) do - ConfigHelper.init_repo_module(__MODULE__, opts) - end - end - - defmodule Arbitrum do - use Ecto.Repo, - otp_app: :explorer, - adapter: Ecto.Adapters.Postgres - - def init(_, opts) do - ConfigHelper.init_repo_module(__MODULE__, opts) - end - end - - defmodule BridgedTokens do - use Ecto.Repo, - otp_app: :explorer, - adapter: Ecto.Adapters.Postgres - - def init(_, opts) do - ConfigHelper.init_repo_module(__MODULE__, opts) - end - end - - defmodule Filecoin do - use Ecto.Repo, - otp_app: :explorer, - adapter: Ecto.Adapters.Postgres - - def init(_, opts) do - ConfigHelper.init_repo_module(__MODULE__, opts) - end - end - - defmodule Stability do - use Ecto.Repo, - otp_app: :explorer, - adapter: Ecto.Adapters.Postgres - - def init(_, opts) do - ConfigHelper.init_repo_module(__MODULE__, opts) - end - end - - defmodule Mud do - use Ecto.Repo, - otp_app: :explorer, - adapter: Ecto.Adapters.Postgres - - def init(_, opts) do - ConfigHelper.init_repo_module(__MODULE__, opts) - end - end - - defmodule ShrunkInternalTransactions do - use Ecto.Repo, - otp_app: :explorer, - adapter: Ecto.Adapters.Postgres - - def init(_, opts) do - ConfigHelper.init_repo_module(__MODULE__, opts) - end - end - - defmodule Blackfort do - use Ecto.Repo, - otp_app: :explorer, - adapter: Ecto.Adapters.Postgres - - def init(_, opts) do - ConfigHelper.init_repo_module(__MODULE__, opts) + for repo <- [ + # Feature dependent repos + Explorer.Repo.Account, + Explorer.Repo.BridgedTokens, + Explorer.Repo.ShrunkInternalTransactions, + + # Chain-type dependent repos + Explorer.Repo.Arbitrum, + Explorer.Repo.Beacon, + Explorer.Repo.Blackfort, + Explorer.Repo.Celo, + Explorer.Repo.Filecoin, + Explorer.Repo.Mud, + Explorer.Repo.Optimism, + Explorer.Repo.PolygonEdge, + Explorer.Repo.PolygonZkevm, + Explorer.Repo.RSK, + Explorer.Repo.Scroll, + Explorer.Repo.Shibarium, + Explorer.Repo.Stability, + Explorer.Repo.Suave, + Explorer.Repo.Zilliqa, + Explorer.Repo.ZkSync + ] do + defmodule repo do + use Ecto.Repo, + otp_app: :explorer, + adapter: Ecto.Adapters.Postgres + + def init(_, opts) do + ConfigHelper.init_repo_module(__MODULE__, opts) + end end end end diff --git a/apps/explorer/priv/zilliqa/migrations/20240927123039_create_quorum_certificate.exs b/apps/explorer/priv/zilliqa/migrations/20240927123039_create_quorum_certificate.exs new file mode 100644 index 0000000000..f3156cae9b --- /dev/null +++ b/apps/explorer/priv/zilliqa/migrations/20240927123039_create_quorum_certificate.exs @@ -0,0 +1,20 @@ +defmodule Explorer.Repo.Zilliqa.Migrations.CreateQuorumCertificate do + use Ecto.Migration + + def change do + create table(:zilliqa_quorum_certificates, primary_key: false) do + add( + :block_hash, + references(:blocks, column: :hash, type: :bytea, on_delete: :delete_all), + null: false, + primary_key: true + ) + + add(:view, :integer, null: false) + add(:signature, :binary, null: false) + add(:signers, {:array, :smallint}, null: false) + + timestamps() + end + end +end diff --git a/apps/explorer/priv/zilliqa/migrations/20240927123101_create_aggregate_quorum_certificate.exs b/apps/explorer/priv/zilliqa/migrations/20240927123101_create_aggregate_quorum_certificate.exs new file mode 100644 index 0000000000..dd21c9cff3 --- /dev/null +++ b/apps/explorer/priv/zilliqa/migrations/20240927123101_create_aggregate_quorum_certificate.exs @@ -0,0 +1,19 @@ +defmodule Explorer.Repo.Zilliqa.Migrations.CreateAggregateQuorumCertificate do + use Ecto.Migration + + def change do + create table(:zilliqa_aggregate_quorum_certificates, primary_key: false) do + add( + :block_hash, + references(:blocks, column: :hash, type: :bytea, on_delete: :delete_all), + null: false, + primary_key: true + ) + + add(:view, :integer, null: false) + add(:signature, :binary, null: false) + + timestamps() + end + end +end diff --git a/apps/explorer/priv/zilliqa/migrations/20240927123113_create_nested_quorum_certificate.exs b/apps/explorer/priv/zilliqa/migrations/20240927123113_create_nested_quorum_certificate.exs new file mode 100644 index 0000000000..01bd2c0422 --- /dev/null +++ b/apps/explorer/priv/zilliqa/migrations/20240927123113_create_nested_quorum_certificate.exs @@ -0,0 +1,21 @@ +defmodule Explorer.Repo.Zilliqa.Migrations.CreateNestedQuorumCertificate do + use Ecto.Migration + + def change do + create table(:zilliqa_nested_quorum_certificates, primary_key: false) do + add( + :block_hash, + references(:blocks, column: :hash, type: :bytea, on_delete: :delete_all), + null: false, + primary_key: true + ) + + add(:proposed_by_validator_index, :smallint, primary_key: true) + add(:view, :integer, null: false) + add(:signature, :binary, null: false) + add(:signers, {:array, :smallint}, null: false) + + timestamps() + end + end +end diff --git a/apps/explorer/priv/zilliqa/migrations/20241015095021_add_view_to_block.exs b/apps/explorer/priv/zilliqa/migrations/20241015095021_add_view_to_block.exs new file mode 100644 index 0000000000..d988964daf --- /dev/null +++ b/apps/explorer/priv/zilliqa/migrations/20241015095021_add_view_to_block.exs @@ -0,0 +1,9 @@ +defmodule Explorer.Repo.Zilliqa.Migrations.AddViewToBlock do + use Ecto.Migration + + def change do + alter table(:blocks) do + add(:zilliqa_view, :integer) + end + end +end diff --git a/apps/explorer/test/explorer/migrator/sanitize_duplicated_log_index_logs_test.exs b/apps/explorer/test/explorer/migrator/sanitize_duplicated_log_index_logs_test.exs index 7d0c5cfb23..5fbdc25a1a 100644 --- a/apps/explorer/test/explorer/migrator/sanitize_duplicated_log_index_logs_test.exs +++ b/apps/explorer/test/explorer/migrator/sanitize_duplicated_log_index_logs_test.exs @@ -32,6 +32,8 @@ defmodule Explorer.Migrator.SanitizeDuplicatedLogIndexLogsTest do updated_logs = Repo.all(Log |> where([log], log.block_number == ^block.number) |> order_by([log], asc: log.index)) + Process.sleep(300) + assert match?( [ %{index: 0, data: %Explorer.Chain.Data{bytes: <<2>>}}, @@ -108,6 +110,8 @@ defmodule Explorer.Migrator.SanitizeDuplicatedLogIndexLogsTest do assert MigrationStatus.get_status("sanitize_duplicated_log_index_logs") == "completed" assert BackgroundMigrations.get_sanitize_duplicated_log_index_logs_finished() == true + Process.sleep(300) + updated_logs = Repo.all(Log |> where([log], log.block_number == ^block.number) |> order_by([log], asc: log.index)) diff --git a/apps/indexer/lib/indexer/block/fetcher.ex b/apps/indexer/lib/indexer/block/fetcher.ex index 7672de69f3..4105e43825 100644 --- a/apps/indexer/lib/indexer/block/fetcher.ex +++ b/apps/indexer/lib/indexer/block/fetcher.ex @@ -145,7 +145,7 @@ defmodule Indexer.Block.Fetcher do additional_options \\ %{} ) when callback_module != nil do - {fetch_time, fetched_blocks} = + {fetch_time, fetch_result} = :timer.tc(fn -> EthereumJSONRPC.fetch_blocks_by_range(range, json_rpc_named_arguments) end) with {:blocks, @@ -156,7 +156,7 @@ defmodule Indexer.Block.Fetcher do withdrawals_params: withdrawals_params, block_second_degree_relations_params: block_second_degree_relations_params, errors: blocks_errors - }}} <- {:blocks, fetched_blocks}, + } = fetched_blocks}} <- {:blocks, fetch_result}, blocks = TransformBlocks.transform_blocks(blocks_params), {:receipts, {:ok, receipt_params}} <- {:receipts, Receipts.fetch(state, transactions_params_without_receipts)}, %{logs: receipt_logs, receipts: receipts} = receipt_params, @@ -247,17 +247,19 @@ defmodule Indexer.Block.Fetcher do token_instances: %{params: token_instances}, signed_authorizations: %{params: SignedAuthorizations.parse(transactions_with_receipts)} }, - chain_type_import_options = %{ - transactions_with_receipts: transactions_with_receipts, - optimism_withdrawals: optimism_withdrawals, - polygon_edge_withdrawals: polygon_edge_withdrawals, - polygon_edge_deposit_executes: polygon_edge_deposit_executes, - polygon_zkevm_bridge_operations: polygon_zkevm_bridge_operations, - scroll_l1_fee_params: scroll_l1_fee_params, - shibarium_bridge_operations: shibarium_bridge_operations, - celo_gas_tokens: celo_gas_tokens, - arbitrum_messages: arbitrum_xlevel_messages - }, + chain_type_import_options = + %{ + transactions_with_receipts: transactions_with_receipts, + optimism_withdrawals: optimism_withdrawals, + polygon_edge_withdrawals: polygon_edge_withdrawals, + polygon_edge_deposit_executes: polygon_edge_deposit_executes, + polygon_zkevm_bridge_operations: polygon_zkevm_bridge_operations, + scroll_l1_fee_params: scroll_l1_fee_params, + shibarium_bridge_operations: shibarium_bridge_operations, + celo_gas_tokens: celo_gas_tokens, + arbitrum_messages: arbitrum_xlevel_messages + } + |> extend_with_zilliqa_import_options(fetched_blocks), {:ok, inserted} <- __MODULE__.import( state, @@ -350,12 +352,33 @@ defmodule Indexer.Block.Fetcher do |> Map.put_new(:arbitrum_messages, %{params: arbitrum_xlevel_messages}) end + :zilliqa -> + defp import_options(basic_import_options, %{ + zilliqa_quorum_certificates: zilliqa_quorum_certificates, + zilliqa_aggregate_quorum_certificates: zilliqa_aggregate_quorum_certificates, + zilliqa_nested_quorum_certificates: zilliqa_nested_quorum_certificates + }) do + basic_import_options + |> Map.put_new(:zilliqa_quorum_certificates, %{params: zilliqa_quorum_certificates}) + |> Map.put_new(:zilliqa_aggregate_quorum_certificates, %{params: zilliqa_aggregate_quorum_certificates}) + |> Map.put_new(:zilliqa_nested_quorum_certificates, %{params: zilliqa_nested_quorum_certificates}) + end + _ -> defp import_options(basic_import_options, _) do basic_import_options end end + defp extend_with_zilliqa_import_options(chain_type_import_options, fetched_blocks) do + chain_type_import_options + |> Map.merge(%{ + zilliqa_quorum_certificates: Map.get(fetched_blocks, :zilliqa_quorum_certificates_params, []), + zilliqa_aggregate_quorum_certificates: Map.get(fetched_blocks, :zilliqa_aggregate_quorum_certificates_params, []), + zilliqa_nested_quorum_certificates: Map.get(fetched_blocks, :zilliqa_nested_quorum_certificates_params, []) + }) + end + defp update_block_cache([]), do: :ok defp update_block_cache(blocks) when is_list(blocks) do diff --git a/config/config_helper.exs b/config/config_helper.exs index 715cfbbdbe..850c3735d8 100644 --- a/config/config_helper.exs +++ b/config/config_helper.exs @@ -9,24 +9,27 @@ defmodule ConfigHelper do def repos do base_repos = [Explorer.Repo, Explorer.Repo.Account] - repos = - case chain_type() do - :ethereum -> base_repos ++ [Explorer.Repo.Beacon] - :optimism -> base_repos ++ [Explorer.Repo.Optimism] - :polygon_edge -> base_repos ++ [Explorer.Repo.PolygonEdge] - :polygon_zkevm -> base_repos ++ [Explorer.Repo.PolygonZkevm] - :rsk -> base_repos ++ [Explorer.Repo.RSK] - :scroll -> base_repos ++ [Explorer.Repo.Scroll] - :shibarium -> base_repos ++ [Explorer.Repo.Shibarium] - :suave -> base_repos ++ [Explorer.Repo.Suave] - :filecoin -> base_repos ++ [Explorer.Repo.Filecoin] - :stability -> base_repos ++ [Explorer.Repo.Stability] - :zksync -> base_repos ++ [Explorer.Repo.ZkSync] - :celo -> base_repos ++ [Explorer.Repo.Celo] - :arbitrum -> base_repos ++ [Explorer.Repo.Arbitrum] - :blackfort -> base_repos ++ [Explorer.Repo.Blackfort] - _ -> base_repos - end + chain_type_repo = + %{ + arbitrum: Explorer.Repo.Arbitrum, + blackfort: Explorer.Repo.Blackfort, + celo: Explorer.Repo.Celo, + ethereum: Explorer.Repo.Beacon, + filecoin: Explorer.Repo.Filecoin, + optimism: Explorer.Repo.Optimism, + polygon_edge: Explorer.Repo.PolygonEdge, + polygon_zkevm: Explorer.Repo.PolygonZkevm, + rsk: Explorer.Repo.RSK, + scroll: Explorer.Repo.Scroll, + shibarium: Explorer.Repo.Shibarium, + stability: Explorer.Repo.Stability, + suave: Explorer.Repo.Suave, + zilliqa: Explorer.Repo.Zilliqa, + zksync: Explorer.Repo.ZkSync + } + |> Map.get(chain_type()) + + chain_type_repos = (chain_type_repo && [chain_type_repo]) || [] ext_repos = [ @@ -37,7 +40,7 @@ defmodule ConfigHelper do |> Enum.filter(&elem(&1, 0)) |> Enum.map(&elem(&1, 1)) - repos ++ ext_repos + base_repos ++ chain_type_repos ++ ext_repos end @spec hackney_options() :: any() @@ -305,6 +308,8 @@ defmodule ConfigHelper do @supported_chain_types [ "default", "arbitrum", + "blackfort", + "celo", "ethereum", "filecoin", "optimism", @@ -316,9 +321,8 @@ defmodule ConfigHelper do "stability", "suave", "zetachain", - "zksync", - "celo", - "blackfort" + "zilliqa", + "zksync" ] @spec chain_type() :: atom() | nil diff --git a/config/runtime/dev.exs b/config/runtime/dev.exs index 198d0efbd3..3f2049a296 100644 --- a/config/runtime/dev.exs +++ b/config/runtime/dev.exs @@ -74,120 +74,6 @@ config :explorer, Explorer.Repo.Account, pool_size: ConfigHelper.parse_integer_env_var("ACCOUNT_POOL_SIZE", 10), queue_target: queue_target -# Configure Beacon Chain database -config :explorer, Explorer.Repo.Beacon, - database: database, - hostname: hostname, - url: System.get_env("DATABASE_URL"), - # actually this repo is not started, and its pool size remains unused. - # separating repos for different CHAIN_TYPE is implemented only for the sake of keeping DB schema update relevant to the current chain type - pool_size: 1 - -# Configures BridgedTokens database -config :explorer, Explorer.Repo.BridgedTokens, - database: database, - hostname: hostname, - url: System.get_env("DATABASE_URL"), - # actually this repo is not started, and its pool size remains unused. - # separating repos for different CHAIN_TYPE is implemented only for the sake of keeping DB schema update relevant to the current chain type - pool_size: 1 - -# Configure Optimism database -config :explorer, Explorer.Repo.Optimism, - database: database, - hostname: hostname, - url: System.get_env("DATABASE_URL"), - pool_size: 1 - -# Configure PolygonEdge database -config :explorer, Explorer.Repo.PolygonEdge, - database: database, - hostname: hostname, - url: System.get_env("DATABASE_URL"), - # actually this repo is not started, and its pool size remains unused. - # separating repos for different CHAIN_TYPE is implemented only for the sake of keeping DB schema update relevant to the current chain type - pool_size: 1 - -# Configure PolygonZkevm database -config :explorer, Explorer.Repo.PolygonZkevm, - database: database, - hostname: hostname, - url: System.get_env("DATABASE_URL"), - # actually this repo is not started, and its pool size remains unused. - # separating repos for different CHAIN_TYPE is implemented only for the sake of keeping DB schema update relevant to the current chain type - pool_size: 1 - -# Configure Scroll database -config :explorer, Explorer.Repo.Scroll, - database: database, - hostname: hostname, - url: System.get_env("DATABASE_URL"), - pool_size: 1 - -# Configure ZkSync database -config :explorer, Explorer.Repo.ZkSync, - database: database, - hostname: hostname, - url: System.get_env("DATABASE_URL"), - # actually this repo is not started, and its pool size remains unused. - # separating repos for different CHAIN_TYPE is implemented only for the sake of keeping DB schema update relevant to the current chain type - pool_size: 1 - -# Configure Celo database -config :explorer, Explorer.Repo.Celo, - database: database, - hostname: hostname, - url: System.get_env("DATABASE_URL"), - # actually this repo is not started, and its pool size remains unused. - # separating repos for different CHAIN_TYPE is implemented only for the sake of keeping DB schema update relevant to the current chain type - pool_size: 1 - -# Configure Rootstock database -config :explorer, Explorer.Repo.RSK, - database: database, - hostname: hostname, - url: System.get_env("DATABASE_URL"), - # actually this repo is not started, and its pool size remains unused. - # separating repos for different CHAIN_TYPE is implemented only for the sake of keeping DB schema update relevant to the current chain type - pool_size: 1 - -# Configure Shibarium database -config :explorer, Explorer.Repo.Shibarium, - database: database, - hostname: hostname, - url: System.get_env("DATABASE_URL"), - pool_size: 1 - -# Configure Suave database -config :explorer, Explorer.Repo.Suave, - database: database, - hostname: hostname, - url: ExplorerConfigHelper.get_suave_db_url(), - pool_size: 1 - -# Configure Filecoin database -config :explorer, Explorer.Repo.Filecoin, - database: database, - hostname: hostname, - url: System.get_env("DATABASE_URL"), - pool_size: 1 - -# Configure Arbitrum database -config :explorer, Explorer.Repo.Arbitrum, - database: database, - hostname: hostname, - url: System.get_env("DATABASE_URL"), - # actually this repo is not started, and its pool size remains unused. - # separating repos for different CHAIN_TYPE is implemented only for the sake of keeping DB schema update relevant to the current chain type - pool_size: 1 - -# Configures Stability database -config :explorer, Explorer.Repo.Stability, - database: database, - hostname: hostname, - url: System.get_env("DATABASE_URL"), - pool_size: 1 - database_mud = if System.get_env("MUD_DATABASE_URL"), do: nil, else: database hostname_mud = if System.get_env("MUD_DATABASE_URL"), do: nil, else: hostname @@ -199,19 +85,42 @@ config :explorer, Explorer.Repo.Mud, pool_size: ConfigHelper.parse_integer_env_var("MUD_POOL_SIZE", 10), queue_target: queue_target -# Configures ShrunkInternalTransactions database -config :explorer, Explorer.Repo.ShrunkInternalTransactions, +# Configure Suave database +config :explorer, Explorer.Repo.Suave, database: database, hostname: hostname, - url: System.get_env("DATABASE_URL"), + url: ExplorerConfigHelper.get_suave_db_url(), pool_size: 1 -# Configures Blackfort database -config :explorer, Explorer.Repo.Blackfort, - database: database, - hostname: hostname, - url: System.get_env("DATABASE_URL"), - pool_size: 1 +# Actually the following repos are not started, and its pool size remains +# unused. Separating repos for different CHAIN_TYPE is implemented only for the +# sake of keeping DB schema update relevant to the current chain type +for repo <- [ + # Chain-type dependent repos + Explorer.Repo.Arbitrum, + Explorer.Repo.Beacon, + Explorer.Repo.Blackfort, + Explorer.Repo.Celo, + Explorer.Repo.Filecoin, + Explorer.Repo.Optimism, + Explorer.Repo.PolygonEdge, + Explorer.Repo.PolygonZkevm, + Explorer.Repo.RSK, + Explorer.Repo.Scroll, + Explorer.Repo.Shibarium, + Explorer.Repo.Stability, + Explorer.Repo.Zilliqa, + Explorer.Repo.ZkSync, + # Feature dependent repos + Explorer.Repo.BridgedTokens, + Explorer.Repo.ShrunkInternalTransactions + ] do + config :explorer, repo, + database: database, + hostname: hostname, + url: System.get_env("DATABASE_URL"), + pool_size: 1 +end variant = Variant.get() diff --git a/config/runtime/prod.exs b/config/runtime/prod.exs index fbb3a50ad2..6225e9f09b 100644 --- a/config/runtime/prod.exs +++ b/config/runtime/prod.exs @@ -52,104 +52,6 @@ config :explorer, Explorer.Repo.Account, ssl: ExplorerConfigHelper.ssl_enabled?(), queue_target: queue_target -# Configure Beacon Chain database -config :explorer, Explorer.Repo.Beacon, - url: System.get_env("DATABASE_URL"), - # actually this repo is not started, and its pool size remains unused. - # separating repos for different CHAIN_TYPE is implemented only for the sake of keeping DB schema update relevant to the current chain type - pool_size: 1, - ssl: ExplorerConfigHelper.ssl_enabled?() - -# Configures BridgedTokens database -config :explorer, Explorer.Repo.BridgedTokens, - url: System.get_env("DATABASE_URL"), - # actually this repo is not started, and its pool size remains unused. - # separating repos for different CHAIN_TYPE is implemented only for the sake of keeping DB schema update relevant to the current chain type - pool_size: 1, - ssl: ExplorerConfigHelper.ssl_enabled?() - -# Configures Optimism database -config :explorer, Explorer.Repo.Optimism, - url: System.get_env("DATABASE_URL"), - pool_size: 1, - ssl: ExplorerConfigHelper.ssl_enabled?() - -# Configures PolygonEdge database -config :explorer, Explorer.Repo.PolygonEdge, - url: System.get_env("DATABASE_URL"), - # actually this repo is not started, and its pool size remains unused. - # separating repos for different CHAIN_TYPE is implemented only for the sake of keeping DB schema update relevant to the current chain type - pool_size: 1, - ssl: ExplorerConfigHelper.ssl_enabled?() - -# Configures PolygonZkevm database -config :explorer, Explorer.Repo.PolygonZkevm, - url: System.get_env("DATABASE_URL"), - pool_size: 1, - ssl: ExplorerConfigHelper.ssl_enabled?() - -# Configures Scroll database -config :explorer, Explorer.Repo.Scroll, - url: System.get_env("DATABASE_URL"), - pool_size: 1, - ssl: ExplorerConfigHelper.ssl_enabled?() - -# Configures ZkSync database -config :explorer, Explorer.Repo.ZkSync, - url: System.get_env("DATABASE_URL"), - # actually this repo is not started, and its pool size remains unused. - # separating repos for different CHAIN_TYPE is implemented only for the sake of keeping DB schema update relevant to the current chain type - pool_size: 1, - ssl: ExplorerConfigHelper.ssl_enabled?() - -# Configures Celo database -config :explorer, Explorer.Repo.Celo, - url: System.get_env("DATABASE_URL"), - # actually this repo is not started, and its pool size remains unused. - # separating repos for different CHAIN_TYPE is implemented only for the sake of keeping DB schema update relevant to the current chain type - pool_size: 1, - ssl: ExplorerConfigHelper.ssl_enabled?() - -# Configures Rootstock database -config :explorer, Explorer.Repo.RSK, - url: System.get_env("DATABASE_URL"), - # actually this repo is not started, and its pool size remains unused. - # separating repos for different CHAIN_TYPE is implemented only for the sake of keeping DB schema update relevant to the current chain type - pool_size: 1, - ssl: ExplorerConfigHelper.ssl_enabled?() - -# Configures Shibarium database -config :explorer, Explorer.Repo.Shibarium, - url: System.get_env("DATABASE_URL"), - pool_size: 1, - ssl: ExplorerConfigHelper.ssl_enabled?() - -# Configures Suave database -config :explorer, Explorer.Repo.Suave, - url: ExplorerConfigHelper.get_suave_db_url(), - pool_size: 1, - ssl: ExplorerConfigHelper.ssl_enabled?() - -# Configures Filecoin database -config :explorer, Explorer.Repo.Filecoin, - url: System.get_env("DATABASE_URL"), - pool_size: 1, - ssl: ExplorerConfigHelper.ssl_enabled?() - -# Configures Arbitrum database -config :explorer, Explorer.Repo.Arbitrum, - url: System.get_env("DATABASE_URL"), - # actually this repo is not started, and its pool size remains unused. - # separating repos for different CHAIN_TYPE is implemented only for the sake of keeping DB schema update relevant to the current chain type - pool_size: 1, - ssl: ExplorerConfigHelper.ssl_enabled?() - -# Configures Stability database -config :explorer, Explorer.Repo.Stability, - url: System.get_env("DATABASE_URL"), - pool_size: 1, - ssl: ExplorerConfigHelper.ssl_enabled?() - # Configures Mud database config :explorer, Explorer.Repo.Mud, url: ExplorerConfigHelper.get_mud_db_url(), @@ -157,19 +59,42 @@ config :explorer, Explorer.Repo.Mud, ssl: ExplorerConfigHelper.ssl_enabled?(), queue_target: queue_target -# Configures ShrunkInternalTransactions database -config :explorer, Explorer.Repo.ShrunkInternalTransactions, - url: System.get_env("DATABASE_URL"), - # actually this repo is not started, and its pool size remains unused. - # separating repos for different CHAIN_TYPE is implemented only for the sake of keeping DB schema update relevant to the current chain type +# Configures Suave database +config :explorer, Explorer.Repo.Suave, + url: ExplorerConfigHelper.get_suave_db_url(), pool_size: 1, ssl: ExplorerConfigHelper.ssl_enabled?() -# Configures Blackfort database -config :explorer, Explorer.Repo.Blackfort, - url: System.get_env("DATABASE_URL"), - pool_size: 1, - ssl: ExplorerConfigHelper.ssl_enabled?() +# Actually the following repos are not started, and its pool size remains +# unused. Separating repos for different chain type or feature flag is +# implemented only for the sake of keeping DB schema update relevant to the +# current chain type +for repo <- [ + # Feature dependent repos + Explorer.Repo.BridgedTokens, + Explorer.Repo.ShrunkInternalTransactions, + + # Chain-type dependent repos + Explorer.Repo.Arbitrum, + Explorer.Repo.Beacon, + Explorer.Repo.Blackfort, + Explorer.Repo.Celo, + Explorer.Repo.Filecoin, + Explorer.Repo.Optimism, + Explorer.Repo.PolygonEdge, + Explorer.Repo.PolygonZkevm, + Explorer.Repo.RSK, + Explorer.Repo.Scroll, + Explorer.Repo.Shibarium, + Explorer.Repo.Stability, + Explorer.Repo.Zilliqa, + Explorer.Repo.ZkSync + ] do + config :explorer, repo, + url: System.get_env("DATABASE_URL"), + pool_size: 1, + ssl: ExplorerConfigHelper.ssl_enabled?() +end variant = Variant.get() diff --git a/cspell.json b/cspell.json index eb1d71f81c..55cd21f2d1 100644 --- a/cspell.json +++ b/cspell.json @@ -64,6 +64,7 @@ "blockscout", "blockscoutuser", "bools", + "Boneh", "bridgedtokenlist", "brotli", "browserconfig", @@ -388,6 +389,7 @@ "pawesome", "paych", "pbcopy", + "PBFT", "peeker", "peekers", "pendingtxlist", @@ -477,6 +479,7 @@ "SENDGRID", "Sepolia", "Sérgio", + "Shacham", "sharelock", "sharelocks", "shibarium", @@ -521,6 +524,7 @@ "subtraces", "successa", "successb", + "supermajority", "supernet", "sushiswap", "swal", @@ -636,6 +640,7 @@ "zetachain", "zftv", "ziczr", + "zilliqa", "zindex", "zipcode", "zkatana",