diff --git a/.dialyzer-ignore b/.dialyzer-ignore index 7ee843f9190e..ba3b24756c02 100644 --- a/.dialyzer-ignore +++ b/.dialyzer-ignore @@ -13,16 +13,14 @@ lib/block_scout_web/schema/types.ex:31 lib/phoenix/router.ex:324 lib/phoenix/router.ex:402 lib/explorer/smart_contract/reader.ex:435 -lib/explorer/exchange_rates/source.ex:139 -lib/explorer/exchange_rates/source.ex:142 lib/indexer/fetcher/polygon_edge.ex:737 -lib/indexer/fetcher/polygon_edge/deposit_execute.ex:140 -lib/indexer/fetcher/polygon_edge/deposit_execute.ex:184 -lib/indexer/fetcher/polygon_edge/withdrawal.ex:160 -lib/indexer/fetcher/polygon_edge/withdrawal.ex:204 +lib/indexer/fetcher/polygon_edge/deposit_execute.ex:146 +lib/indexer/fetcher/polygon_edge/deposit_execute.ex:190 +lib/indexer/fetcher/polygon_edge/withdrawal.ex:166 +lib/indexer/fetcher/polygon_edge/withdrawal.ex:210 lib/indexer/fetcher/zkevm/transaction_batch.ex:116 lib/indexer/fetcher/zkevm/transaction_batch.ex:156 lib/indexer/fetcher/zkevm/transaction_batch.ex:252 lib/block_scout_web/views/api/v2/transaction_view.ex:431 lib/block_scout_web/views/api/v2/transaction_view.ex:472 -lib/explorer/chain/transaction.ex:167 +lib/explorer/chain/transaction.ex:170 diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index 1888fe929049..dcd9fc29d2b7 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -24,8 +24,8 @@ body: description: How the application has been deployed. options: - Docker-compose + - Helm charts (k8s) - Manual from the source code - - Helm charts - Docker validations: required: true @@ -65,7 +65,7 @@ body: attributes: label: Backend version description: The release version of the backend or branch/commit. - placeholder: v5.3.2 + placeholder: v6.0.0 validations: required: true diff --git a/.github/workflows/config.yml b/.github/workflows/config.yml index 31e89c223fa4..82c11d6d85ec 100644 --- a/.github/workflows/config.yml +++ b/.github/workflows/config.yml @@ -4,21 +4,22 @@ on: push: branches: - master - - production-core-stg - - production-eth-stg-experimental - - production-eth-goerli-stg - - production-eth-sepolia-stg - - production-fuse-stg - - production-optimism-stg - - production-immutable-stg - - production-iota-stg - - production-lukso-stg - - production-rsk-stg - - production-sokol-stg - - production-suave-stg - - production-xdai-stg - - production-zkevm-stg - - production-zksync-stg + - v6.0.0-dev + - production-core + - production-eth-experimental + - production-eth-goerli + - production-eth-sepolia + - production-fuse + - production-optimism + - production-immutable + - production-iota + - production-lukso + - production-rsk + - production-sokol + - production-suave + - production-xdai + - production-zkevm + - production-zksync - staging-l2 paths-ignore: - 'CHANGELOG.md' @@ -28,7 +29,9 @@ on: pull_request: branches: - master - - production-optimism-stg + - v6.0.0-dev + - production-optimism + - production-zksync env: MIX_ENV: test @@ -72,7 +75,7 @@ jobs: path: | deps _build - key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_27-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_33-${{ hashFiles('mix.lock') }} restore-keys: | ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps- @@ -130,7 +133,7 @@ jobs: path: | deps _build - key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_27-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_33-${{ hashFiles('mix.lock') }} restore-keys: | ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-" @@ -154,7 +157,7 @@ jobs: path: | deps _build - key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_27-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_33-${{ hashFiles('mix.lock') }} restore-keys: | ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-" @@ -183,7 +186,7 @@ jobs: path: | deps _build - key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_27-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_33-${{ hashFiles('mix.lock') }} restore-keys: | ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-" @@ -227,7 +230,7 @@ jobs: path: | deps _build - key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_27-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_33-${{ hashFiles('mix.lock') }} restore-keys: | ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-" @@ -253,7 +256,7 @@ jobs: path: | deps _build - key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_27-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_33-${{ hashFiles('mix.lock') }} restore-keys: | ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-" @@ -282,7 +285,7 @@ jobs: path: | deps _build - key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_27-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_33-${{ hashFiles('mix.lock') }} restore-keys: | ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-" @@ -330,7 +333,7 @@ jobs: path: | deps _build - key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_27-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_33-${{ hashFiles('mix.lock') }} restore-keys: | ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-" @@ -376,7 +379,7 @@ jobs: path: | deps _build - key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_27-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_33-${{ hashFiles('mix.lock') }} restore-keys: | ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-" @@ -438,7 +441,7 @@ jobs: path: | deps _build - key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_27-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_33-${{ hashFiles('mix.lock') }} restore-keys: | ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-" @@ -498,7 +501,7 @@ jobs: path: | deps _build - key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_27-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_33-${{ hashFiles('mix.lock') }} restore-keys: | ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-" @@ -569,7 +572,7 @@ jobs: path: | deps _build - key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_27-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_33-${{ hashFiles('mix.lock') }} restore-keys: | ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-" @@ -637,7 +640,7 @@ jobs: path: | deps _build - key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_27-${{ hashFiles('mix.lock') }} + key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_33-${{ hashFiles('mix.lock') }} restore-keys: | ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-" diff --git a/.github/workflows/prerelease-main.yml b/.github/workflows/prerelease.yml similarity index 93% rename from .github/workflows/prerelease-main.yml rename to .github/workflows/prerelease.yml index 57890b3ffb9b..2d09956875c9 100644 --- a/.github/workflows/prerelease-main.yml +++ b/.github/workflows/prerelease.yml @@ -1,4 +1,4 @@ -name: Pre-release main +name: Pre-release master on: workflow_dispatch: @@ -16,7 +16,7 @@ jobs: name: Push Docker image to Docker Hub runs-on: ubuntu-latest env: - RELEASE_VERSION: 5.3.2 + RELEASE_VERSION: 6.0.0 steps: - name: Check out the repo uses: actions/checkout@v4 @@ -61,5 +61,5 @@ jobs: AMPLITUDE_URL= AMPLITUDE_API_KEY= CACHE_ADDRESS_WITH_BALANCES_UPDATE_INTERVAL= - BLOCKSCOUT_VERSION=v${{ env.RELEASE_VERSION }}-beta + BLOCKSCOUT_VERSION=v${{ env.RELEASE_VERSION }}-alpha RELEASE_VERSION=${{ env.RELEASE_VERSION }} \ No newline at end of file diff --git a/.github/workflows/publish-docker-image-every-push.yml b/.github/workflows/publish-docker-image-every-push.yml index a5ebce6f57f6..25d9c3fa9960 100644 --- a/.github/workflows/publish-docker-image-every-push.yml +++ b/.github/workflows/publish-docker-image-every-push.yml @@ -11,7 +11,7 @@ on: env: OTP_VERSION: '25.2.1' ELIXIR_VERSION: '1.14.5' - RELEASE_VERSION: 5.3.2 + RELEASE_VERSION: 6.0.0 jobs: push_to_registry: diff --git a/.github/workflows/publish-docker-image-for-core.yml b/.github/workflows/publish-docker-image-for-core.yml index b90942317baf..61cb6f8189a2 100644 --- a/.github/workflows/publish-docker-image-for-core.yml +++ b/.github/workflows/publish-docker-image-for-core.yml @@ -9,13 +9,13 @@ on: workflow_dispatch: push: branches: - - production-core-stg + - production-core jobs: push_to_registry: name: Push Docker image to Docker Hub runs-on: ubuntu-latest env: - RELEASE_VERSION: 5.3.2 + RELEASE_VERSION: 6.0.0 DOCKER_CHAIN_NAME: poa steps: - name: Check out the repo diff --git a/.github/workflows/publish-docker-image-for-eth-goerli.yml b/.github/workflows/publish-docker-image-for-eth-goerli.yml index 28b51f91c9ed..f381283cf359 100644 --- a/.github/workflows/publish-docker-image-for-eth-goerli.yml +++ b/.github/workflows/publish-docker-image-for-eth-goerli.yml @@ -9,13 +9,13 @@ on: workflow_dispatch: push: branches: - - production-eth-goerli-stg + - production-eth-goerli jobs: push_to_registry: name: Push Docker image to Docker Hub runs-on: ubuntu-latest env: - RELEASE_VERSION: 5.3.2 + RELEASE_VERSION: 6.0.0 DOCKER_CHAIN_NAME: eth-goerli steps: - name: Check out the repo diff --git a/.github/workflows/publish-docker-image-for-eth-sepolia.yml b/.github/workflows/publish-docker-image-for-eth-sepolia.yml index 28bedb51ee75..b24c2571676e 100644 --- a/.github/workflows/publish-docker-image-for-eth-sepolia.yml +++ b/.github/workflows/publish-docker-image-for-eth-sepolia.yml @@ -9,13 +9,13 @@ on: workflow_dispatch: push: branches: - - production-eth-sepolia-stg + - production-eth-sepolia jobs: push_to_registry: name: Push Docker image to Docker Hub runs-on: ubuntu-latest env: - RELEASE_VERSION: 5.3.2 + RELEASE_VERSION: 6.0.0 DOCKER_CHAIN_NAME: eth-sepolia steps: - name: Check out the repo diff --git a/.github/workflows/publish-docker-image-for-eth.yml b/.github/workflows/publish-docker-image-for-eth.yml index bfd8ae15198d..a663380306f2 100644 --- a/.github/workflows/publish-docker-image-for-eth.yml +++ b/.github/workflows/publish-docker-image-for-eth.yml @@ -9,13 +9,13 @@ on: workflow_dispatch: push: branches: - - production-eth-stg-experimental + - production-eth-experimental jobs: push_to_registry: name: Push Docker image to Docker Hub runs-on: ubuntu-latest env: - RELEASE_VERSION: 5.3.2 + RELEASE_VERSION: 6.0.0 DOCKER_CHAIN_NAME: mainnet steps: - name: Check out the repo diff --git a/.github/workflows/publish-docker-image-for-fuse.yml b/.github/workflows/publish-docker-image-for-fuse.yml index b0d1eb68ff1e..a9ec8713e782 100644 --- a/.github/workflows/publish-docker-image-for-fuse.yml +++ b/.github/workflows/publish-docker-image-for-fuse.yml @@ -9,13 +9,13 @@ on: workflow_dispatch: push: branches: - - production-fuse-stg + - production-fuse jobs: push_to_registry: name: Push Docker image to Docker Hub runs-on: ubuntu-latest env: - RELEASE_VERSION: 5.3.2 + RELEASE_VERSION: 6.0.0 DOCKER_CHAIN_NAME: fuse steps: - name: Check out the repo diff --git a/.github/workflows/publish-docker-image-for-immutable.yml b/.github/workflows/publish-docker-image-for-immutable.yml index d86f7025cbc8..8bd44e3d1ccd 100644 --- a/.github/workflows/publish-docker-image-for-immutable.yml +++ b/.github/workflows/publish-docker-image-for-immutable.yml @@ -9,13 +9,13 @@ on: workflow_dispatch: push: branches: - - production-immutable-stg + - production-immutable jobs: push_to_registry: name: Push Docker image to Docker Hub runs-on: ubuntu-latest env: - RELEASE_VERSION: 5.3.2 + RELEASE_VERSION: 6.0.0 DOCKER_CHAIN_NAME: immutable steps: - name: Check out the repo diff --git a/.github/workflows/publish-docker-image-for-l2-staging.yml b/.github/workflows/publish-docker-image-for-l2-staging.yml index a6227eaee50a..1c5f290907a6 100644 --- a/.github/workflows/publish-docker-image-for-l2-staging.yml +++ b/.github/workflows/publish-docker-image-for-l2-staging.yml @@ -15,7 +15,7 @@ jobs: name: Push Docker image to Docker Hub runs-on: ubuntu-latest env: - RELEASE_VERSION: 5.3.2 + RELEASE_VERSION: 6.0.0 DOCKER_CHAIN_NAME: optimism-l2-advanced steps: - name: Check out the repo diff --git a/.github/workflows/publish-docker-image-for-lukso.yml b/.github/workflows/publish-docker-image-for-lukso.yml index f294165468c7..10efc69fe25a 100644 --- a/.github/workflows/publish-docker-image-for-lukso.yml +++ b/.github/workflows/publish-docker-image-for-lukso.yml @@ -9,13 +9,13 @@ on: workflow_dispatch: push: branches: - - production-lukso-stg + - production-lukso jobs: push_to_registry: name: Push Docker image to Docker Hub runs-on: ubuntu-latest env: - RELEASE_VERSION: 5.3.2 + RELEASE_VERSION: 6.0.0 DOCKER_CHAIN_NAME: lukso steps: - name: Check out the repo diff --git a/.github/workflows/publish-docker-image-for-optimism.yml b/.github/workflows/publish-docker-image-for-optimism.yml index c164e1b7d8eb..f0c24fa58221 100644 --- a/.github/workflows/publish-docker-image-for-optimism.yml +++ b/.github/workflows/publish-docker-image-for-optimism.yml @@ -9,13 +9,13 @@ on: workflow_dispatch: push: branches: - - production-optimism-stg + - production-optimism jobs: push_to_registry: name: Push Docker image to Docker Hub runs-on: ubuntu-latest env: - RELEASE_VERSION: 5.3.2 + RELEASE_VERSION: 6.0.0 DOCKER_CHAIN_NAME: optimism steps: - name: Check out the repo diff --git a/.github/workflows/publish-docker-image-for-polygon-edge.yml b/.github/workflows/publish-docker-image-for-polygon-edge.yml index 3b3f7604ca6e..a7384c4f29fc 100644 --- a/.github/workflows/publish-docker-image-for-polygon-edge.yml +++ b/.github/workflows/publish-docker-image-for-polygon-edge.yml @@ -9,13 +9,13 @@ on: workflow_dispatch: push: branches: - - production-polygon-edge-stg + - production-polygon-edge jobs: push_to_registry: name: Push Docker image to Docker Hub runs-on: ubuntu-latest env: - RELEASE_VERSION: 5.3.2 + RELEASE_VERSION: 6.0.0 DOCKER_CHAIN_NAME: polygon-edge steps: - name: Check out the repo diff --git a/.github/workflows/publish-docker-image-for-rsk.yml b/.github/workflows/publish-docker-image-for-rsk.yml index d47134f2ae40..615ad8820bca 100644 --- a/.github/workflows/publish-docker-image-for-rsk.yml +++ b/.github/workflows/publish-docker-image-for-rsk.yml @@ -9,13 +9,13 @@ on: workflow_dispatch: push: branches: - - production-rsk-stg + - production-rsk jobs: push_to_registry: name: Push Docker image to Docker Hub runs-on: ubuntu-latest env: - RELEASE_VERSION: 5.3.2 + RELEASE_VERSION: 6.0.0 DOCKER_CHAIN_NAME: rsk steps: - name: Check out the repo diff --git a/.github/workflows/publish-docker-image-for-stability.yml b/.github/workflows/publish-docker-image-for-stability.yml index 089e5924e3d4..c14e5a6a6ac9 100644 --- a/.github/workflows/publish-docker-image-for-stability.yml +++ b/.github/workflows/publish-docker-image-for-stability.yml @@ -9,7 +9,7 @@ on: workflow_dispatch: push: branches: - - production-stability-stg + - production-stability env: OTP_VERSION: '25.2.1' ELIXIR_VERSION: '1.14.5' @@ -18,7 +18,7 @@ jobs: name: Push Docker image to Docker Hub runs-on: ubuntu-latest env: - RELEASE_VERSION: 5.3.2 + RELEASE_VERSION: 6.0.0 DOCKER_CHAIN_NAME: stability steps: - name: Check out the repo diff --git a/.github/workflows/publish-docker-image-for-suave.yml b/.github/workflows/publish-docker-image-for-suave.yml index b91bb91e546f..0f3c4ad43982 100644 --- a/.github/workflows/publish-docker-image-for-suave.yml +++ b/.github/workflows/publish-docker-image-for-suave.yml @@ -9,7 +9,7 @@ on: workflow_dispatch: push: branches: - - production-suave-stg + - production-suave env: OTP_VERSION: '25.2.1' ELIXIR_VERSION: '1.14.5' @@ -18,7 +18,7 @@ jobs: name: Push Docker image to Docker Hub runs-on: ubuntu-latest env: - RELEASE_VERSION: 5.3.2 + RELEASE_VERSION: 6.0.0 DOCKER_CHAIN_NAME: suave steps: - name: Check out the repo diff --git a/.github/workflows/publish-docker-image-for-xdai.yml b/.github/workflows/publish-docker-image-for-xdai.yml index 947a675c57fe..b53fe18fc50f 100644 --- a/.github/workflows/publish-docker-image-for-xdai.yml +++ b/.github/workflows/publish-docker-image-for-xdai.yml @@ -9,13 +9,13 @@ on: workflow_dispatch: push: branches: - - production-xdai-stg + - production-xdai jobs: push_to_registry: name: Push Docker image to Docker Hub runs-on: ubuntu-latest env: - RELEASE_VERSION: 5.3.2 + RELEASE_VERSION: 6.0.0 DOCKER_CHAIN_NAME: xdai steps: - name: Check out the repo diff --git a/.github/workflows/publish-docker-image-for-zkevm.yml b/.github/workflows/publish-docker-image-for-zkevm.yml index 32a17480e8e5..0e7072a3940e 100644 --- a/.github/workflows/publish-docker-image-for-zkevm.yml +++ b/.github/workflows/publish-docker-image-for-zkevm.yml @@ -9,13 +9,13 @@ on: workflow_dispatch: push: branches: - - production-zkevm-stg + - production-zkevm jobs: push_to_registry: name: Push Docker image to Docker Hub runs-on: ubuntu-latest env: - RELEASE_VERSION: 5.3.2 + RELEASE_VERSION: 6.0.0 DOCKER_CHAIN_NAME: zkevm steps: - name: Check out the repo diff --git a/.github/workflows/publish-docker-image-for-zksync.yml b/.github/workflows/publish-docker-image-for-zksync.yml index 8d02b444c17a..04cdf569a63d 100644 --- a/.github/workflows/publish-docker-image-for-zksync.yml +++ b/.github/workflows/publish-docker-image-for-zksync.yml @@ -9,13 +9,13 @@ on: workflow_dispatch: push: branches: - - production-zksync-stg + - production-zksync jobs: push_to_registry: name: Push Docker image to Docker Hub runs-on: ubuntu-latest env: - RELEASE_VERSION: 5.3.2 + RELEASE_VERSION: 6.0.0 DOCKER_CHAIN_NAME: zksync steps: - name: Check out the repo diff --git a/.github/workflows/publish-docker-image-staging-on-demand.yml b/.github/workflows/publish-docker-image-staging-on-demand.yml index ad6b96a3240b..090a6be9d782 100644 --- a/.github/workflows/publish-docker-image-staging-on-demand.yml +++ b/.github/workflows/publish-docker-image-staging-on-demand.yml @@ -12,7 +12,7 @@ on: env: OTP_VERSION: '25.2.1' ELIXIR_VERSION: '1.14.5' - RELEASE_VERSION: 5.3.2 + RELEASE_VERSION: 6.0.0 jobs: push_to_registry: diff --git a/.github/workflows/release-additional.yml b/.github/workflows/release-additional.yml index 72c287e93730..f0912540dc69 100644 --- a/.github/workflows/release-additional.yml +++ b/.github/workflows/release-additional.yml @@ -18,7 +18,7 @@ jobs: name: Push Docker image to Docker Hub runs-on: ubuntu-latest env: - RELEASE_VERSION: 5.3.2 + RELEASE_VERSION: 6.0.0 steps: - name: Check out the repo uses: actions/checkout@v4 diff --git a/.github/workflows/release-main.yml b/.github/workflows/release.yml similarity index 93% rename from .github/workflows/release-main.yml rename to .github/workflows/release.yml index 60c840a7f70b..ded48ce497f3 100644 --- a/.github/workflows/release-main.yml +++ b/.github/workflows/release.yml @@ -3,7 +3,7 @@ # separate terms of service, privacy policy, and support # documentation. -name: Release main +name: Release on: release: @@ -18,7 +18,7 @@ jobs: name: Push Docker image to Docker Hub runs-on: ubuntu-latest env: - RELEASE_VERSION: 5.3.2 + RELEASE_VERSION: 6.0.0 steps: - name: Check out the repo uses: actions/checkout@v4 @@ -127,15 +127,15 @@ jobs: # runs-on: ubuntu-latest # env: # BRANCHES: | - # production-core-stg - # production-sokol-stg - # production-eth-stg-experimental - # production-eth-goerli-stg - # production-lukso-stg - # production-xdai-stg - # production-polygon-supernets-stg - # production-rsk-stg - # production-immutable-stg + # production-core + # production-sokol + # production-eth-experimental + # production-eth-goerli + # production-lukso + # production-xdai + # production-polygon-supernets + # production-rsk + # production-immutable # steps: # - uses: actions/checkout@v4 # - name: Set Git config diff --git a/.gitignore b/.gitignore index 9e53084c62bb..bfd06c0a3aac 100644 --- a/.gitignore +++ b/.gitignore @@ -60,3 +60,8 @@ dump.rdb .vscode **.dec** + +*.env +*.env.example +*.env.local +*.env.staging diff --git a/CHANGELOG.md b/CHANGELOG.md index efac2523b600..cf23936d0a38 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,8 +6,156 @@ ### Fixes +- [#9075](https://github.com/blockscout/blockscout/pull/9075) - Fix fetching contract codes + +### Chore + +
+ Dependencies version bumps + +
+ +## 6.0.0 + +### Features + +- [#9112](https://github.com/blockscout/blockscout/pull/9112) - Add specific url for eth_call +- [#9044](https://github.com/blockscout/blockscout/pull/9044) - Expand gas price oracle functionality + +### Fixes + +- [#9113](https://github.com/blockscout/blockscout/pull/9113) - Fix migrators cache updating +- [#9101](https://github.com/blockscout/blockscout/pull/9101) - Fix migration_finished? logic +- [#9062](https://github.com/blockscout/blockscout/pull/9062) - Fix blockscout-ens integration +- [#9061](https://github.com/blockscout/blockscout/pull/9061) - Arbitrum allow tx receipt gasUsedForL1 field +- [#8812](https://github.com/blockscout/blockscout/pull/8812) - Update existing tokens type if got transfer with higher type priority + +### Chore + +- [#9055](https://github.com/blockscout/blockscout/pull/9055) - Add ASC indices for logs, token transfers, transactions +- [#9038](https://github.com/blockscout/blockscout/pull/9038) - Token type filling migrations +- [#9009](https://github.com/blockscout/blockscout/pull/9009) - Index for block refetch_needed +- [#9007](https://github.com/blockscout/blockscout/pull/9007) - Drop logs type index +- [#9006](https://github.com/blockscout/blockscout/pull/9006) - Drop unused indexes on address_current_token_balances table +- [#9005](https://github.com/blockscout/blockscout/pull/9005) - Drop unused token_id column from token_transfers table and indexes based on this column +- [#9000](https://github.com/blockscout/blockscout/pull/9000) - Change log topic type in the DB to bytea +- [#8996](https://github.com/blockscout/blockscout/pull/8996) - Refine token transfers token ids index +- [#5322](https://github.com/blockscout/blockscout/pull/5322) - DB denormalization: block consensus and timestamp in transaction table + +
+ Dependencies version bumps + +- [#9059](https://github.com/blockscout/blockscout/pull/9059) - Bump redux from 5.0.0 to 5.0.1 in /apps/block_scout_web/assets +- [#9057](https://github.com/blockscout/blockscout/pull/9057) - Bump benchee from 1.2.0 to 1.3.0 +- [#9060](https://github.com/blockscout/blockscout/pull/9060) - Bump @amplitude/analytics-browser from 2.3.7 to 2.3.8 in /apps/block_scout_web/assets +- [#9084](https://github.com/blockscout/blockscout/pull/9084) - Bump @babel/preset-env from 7.23.6 to 7.23.7 in /apps/block_scout_web/assets +- [#9083](https://github.com/blockscout/blockscout/pull/9083) - Bump @babel/core from 7.23.6 to 7.23.7 in /apps/block_scout_web/assets +- [#9086](https://github.com/blockscout/blockscout/pull/9086) - Bump core-js from 3.34.0 to 3.35.0 in /apps/block_scout_web/assets +- [#9081](https://github.com/blockscout/blockscout/pull/9081) - Bump sweetalert2 from 11.10.1 to 11.10.2 in /apps/block_scout_web/assets +- [#9085](https://github.com/blockscout/blockscout/pull/9085) - Bump moment from 2.29.4 to 2.30.1 in /apps/block_scout_web/assets +- [#9087](https://github.com/blockscout/blockscout/pull/9087) - Bump postcss-loader from 7.3.3 to 7.3.4 in /apps/block_scout_web/assets +- [#9082](https://github.com/blockscout/blockscout/pull/9082) - Bump sass-loader from 13.3.2 to 13.3.3 in /apps/block_scout_web/assets +- [#9088](https://github.com/blockscout/blockscout/pull/9088) - Bump sass from 1.69.5 to 1.69.6 in /apps/block_scout_web/assets + +
+ +## 5.4.0-beta + +### Features + +- [#9018](https://github.com/blockscout/blockscout/pull/9018) - Add SmartContractRealtimeEventHandler +- [#8997](https://github.com/blockscout/blockscout/pull/8997) - Isolate throttable error count by request method +- [#8975](https://github.com/blockscout/blockscout/pull/8975) - Add EIP-4844 compatibility (not full support yet) +- [#8972](https://github.com/blockscout/blockscout/pull/8972) - BENS integration +- [#8960](https://github.com/blockscout/blockscout/pull/8960) - TRACE_BLOCK_RANGES env var +- [#8957](https://github.com/blockscout/blockscout/pull/8957) - Add Tx Interpreter Service integration + +### Fixes + +- [#9039](https://github.com/blockscout/blockscout/pull/9039) - Fix tx input decoding in tx summary microservice request +- [#9035](https://github.com/blockscout/blockscout/pull/9035) - Handle Postgrex errors on NFT import +- [#9015](https://github.com/blockscout/blockscout/pull/9015) - Optimize NFT owner preload +- [#9013](https://github.com/blockscout/blockscout/pull/9013) - Speed up `Indexer.Fetcher.TokenInstance.LegacySanitize` +- [#8969](https://github.com/blockscout/blockscout/pull/8969) - Support legacy paging options for address transaction endpoint +- [#8965](https://github.com/blockscout/blockscout/pull/8965) - Set poll: false for internal transactions fetcher +- [#8955](https://github.com/blockscout/blockscout/pull/8955) - Remove daily balances updating from BlockReward fetcher +- [#8846](https://github.com/blockscout/blockscout/pull/8846) - Handle nil gas_price at address view + ### Chore +- [#9094](https://github.com/blockscout/blockscout/pull/9094) - Improve exchange rates logging +- [#9014](https://github.com/blockscout/blockscout/pull/9014) - Decrease amount of NFT in address collection: 15 -> 9 +- [#8994](https://github.com/blockscout/blockscout/pull/8994) - Refactor transactions event preloads +- [#8991](https://github.com/blockscout/blockscout/pull/8991) - Manage DB queue target via runtime env var + +
+ Dependencies version bumps + +- [#8986](https://github.com/blockscout/blockscout/pull/8986) - Bump chart.js from 4.4.0 to 4.4.1 in /apps/block_scout_web/assets +- [#8982](https://github.com/blockscout/blockscout/pull/8982) - Bump ex_doc from 0.30.9 to 0.31.0 +- [#8987](https://github.com/blockscout/blockscout/pull/8987) - Bump @babel/preset-env from 7.23.5 to 7.23.6 in /apps/block_scout_web/assets +- [#8984](https://github.com/blockscout/blockscout/pull/8984) - Bump ecto_sql from 3.11.0 to 3.11.1 +- [#8988](https://github.com/blockscout/blockscout/pull/8988) - Bump core-js from 3.33.3 to 3.34.0 in /apps/block_scout_web/assets +- [#8980](https://github.com/blockscout/blockscout/pull/8980) - Bump exvcr from 0.14.4 to 0.15.0 +- [#8985](https://github.com/blockscout/blockscout/pull/8985) - Bump @babel/core from 7.23.5 to 7.23.6 in /apps/block_scout_web/assets +- [#9020](https://github.com/blockscout/blockscout/pull/9020) - Bump eslint-plugin-import from 2.29.0 to 2.29.1 in /apps/block_scout_web/assets +- [#9021](https://github.com/blockscout/blockscout/pull/9021) - Bump eslint from 8.55.0 to 8.56.0 in /apps/block_scout_web/assets +- [#9019](https://github.com/blockscout/blockscout/pull/9019) - Bump @amplitude/analytics-browser from 2.3.6 to 2.3.7 in /apps/block_scout_web/assets + +
+ +## 5.3.3-beta + +### Features + +- [#8966](https://github.com/blockscout/blockscout/pull/8966) - Add `ACCOUNT_WATCHLIST_NOTIFICATIONS_LIMIT_FOR_30_DAYS` +- [#8908](https://github.com/blockscout/blockscout/pull/8908) - Solidityscan report API endpoint +- [#8900](https://github.com/blockscout/blockscout/pull/8900) - Add Compound proxy contract pattern +- [#8611](https://github.com/blockscout/blockscout/pull/8611) - Implement sorting of smart contracts, address transactions + +### Fixes + +- [#8959](https://github.com/blockscout/blockscout/pull/8959) - Skip failed instances in Token Instance Owner migrator +- [#8924](https://github.com/blockscout/blockscout/pull/8924) - Delete invalid current token balances in OnDemand fetcher +- [#8922](https://github.com/blockscout/blockscout/pull/8922) - Allow call type to be in lowercase +- [#8917](https://github.com/blockscout/blockscout/pull/8917) - Proxy detection hotfix in API v2 +- [#8915](https://github.com/blockscout/blockscout/pull/8915) - smart-contract: delete embeds_many relation on replace +- [#8906](https://github.com/blockscout/blockscout/pull/8906) - Fix abi encoded string argument +- [#8898](https://github.com/blockscout/blockscout/pull/8898) - Enhance method decoding by candidates from DB +- [#8882](https://github.com/blockscout/blockscout/pull/8882) - Change order of proxy contracts patterns detection: existing popular EIPs to the top of the list +- [#8707](https://github.com/blockscout/blockscout/pull/8707) - Fix native coin exchange rate with `EXCHANGE_RATES_COINGECKO_COIN_ID` + +### Chore + +- [#8956](https://github.com/blockscout/blockscout/pull/8956) - Refine docker-compose config structure +- [#8911](https://github.com/blockscout/blockscout/pull/8911) - Set client_connection_check_interval for main Postgres DB in docker-compose setup + +
+ Dependencies version bumps + +- [#8863](https://github.com/blockscout/blockscout/pull/8863) - Bump core-js from 3.33.2 to 3.33.3 in /apps/block_scout_web/assets +- [#8864](https://github.com/blockscout/blockscout/pull/8864) - Bump @amplitude/analytics-browser from 2.3.3 to 2.3.5 in /apps/block_scout_web/assets +- [#8860](https://github.com/blockscout/blockscout/pull/8860) - Bump ecto_sql from 3.10.2 to 3.11.0 +- [#8896](https://github.com/blockscout/blockscout/pull/8896) - Bump httpoison from 2.2.0 to 2.2.1 +- [#8867](https://github.com/blockscout/blockscout/pull/8867) - Bump mixpanel-browser from 2.47.0 to 2.48.1 in /apps/block_scout_web/assets +- [#8865](https://github.com/blockscout/blockscout/pull/8865) - Bump eslint from 8.53.0 to 8.54.0 in /apps/block_scout_web/assets +- [#8866](https://github.com/blockscout/blockscout/pull/8866) - Bump sweetalert2 from 11.9.0 to 11.10.1 in /apps/block_scout_web/assets +- [#8897](https://github.com/blockscout/blockscout/pull/8897) - Bump prometheus from 4.10.0 to 4.11.0 +- [#8859](https://github.com/blockscout/blockscout/pull/8859) - Bump absinthe from 1.7.5 to 1.7.6 +- [#8858](https://github.com/blockscout/blockscout/pull/8858) - Bump ex_json_schema from 0.10.1 to 0.10.2 +- [#8943](https://github.com/blockscout/blockscout/pull/8943) - Bump postgrex from 0.17.3 to 0.17.4 +- [#8939](https://github.com/blockscout/blockscout/pull/8939) - Bump @babel/core from 7.23.3 to 7.23.5 in /apps/block_scout_web/assets +- [#8936](https://github.com/blockscout/blockscout/pull/8936) - Bump eslint from 8.54.0 to 8.55.0 in /apps/block_scout_web/assets +- [#8940](https://github.com/blockscout/blockscout/pull/8940) - Bump photoswipe from 5.4.2 to 5.4.3 in /apps/block_scout_web/assets +- [#8938](https://github.com/blockscout/blockscout/pull/8938) - Bump @babel/preset-env from 7.23.3 to 7.23.5 in /apps/block_scout_web/assets +- [#8935](https://github.com/blockscout/blockscout/pull/8935) - Bump @amplitude/analytics-browser from 2.3.5 to 2.3.6 in /apps/block_scout_web/assets +- [#8937](https://github.com/blockscout/blockscout/pull/8937) - Bump redux from 4.2.1 to 5.0.0 in /apps/block_scout_web/assets +- [#8942](https://github.com/blockscout/blockscout/pull/8942) - Bump gettext from 0.23.1 to 0.24.0 +- [#8934](https://github.com/blockscout/blockscout/pull/8934) - Bump @fortawesome/fontawesome-free from 6.4.2 to 6.5.1 in /apps/block_scout_web/assets +- [#8933](https://github.com/blockscout/blockscout/pull/8933) - Bump postcss from 8.4.31 to 8.4.32 in /apps/block_scout_web/assets + +
+ ## 5.3.2-beta ### Features diff --git a/README.md b/README.md index 91130f13aaec..678d7f78d118 100644 --- a/README.md +++ b/README.md @@ -28,9 +28,11 @@ Blockscout currently supports several hundred chains and rollups throughout the See the [project documentation](https://docs.blockscout.com/) for instructions: -- [Requirements](https://docs.blockscout.com/for-developers/information-and-settings/requirements) +- [Manual deployment](https://docs.blockscout.com/for-developers/deployment/manual-deployment-guide) +- [Docker-compose deployment](https://docs.blockscout.com/for-developers/deployment/docker-compose-deployment) +- [Kubernetes deployment](https://docs.blockscout.com/for-developers/deployment/kubernetes-deployment) +- [Manual deployment (backend + old UI)](https://docs.blockscout.com/for-developers/deployment/manual-old-ui) - [Ansible deployment](https://docs.blockscout.com/for-developers/ansible-deployment) -- [Manual deployment](https://docs.blockscout.com/for-developers/manual-deployment) - [ENV variables](https://docs.blockscout.com/for-developers/information-and-settings/env-variables) - [Configuration options](https://docs.blockscout.com/for-developers/configuration-options) diff --git a/apps/block_scout_web/assets/package-lock.json b/apps/block_scout_web/assets/package-lock.json index f0b0d1b85c88..1ad62aa64380 100644 --- a/apps/block_scout_web/assets/package-lock.json +++ b/apps/block_scout_web/assets/package-lock.json @@ -7,17 +7,17 @@ "name": "blockscout", "license": "GPL-3.0", "dependencies": { - "@amplitude/analytics-browser": "^2.3.5", - "@fortawesome/fontawesome-free": "^6.4.2", + "@amplitude/analytics-browser": "^2.3.8", + "@fortawesome/fontawesome-free": "^6.5.1", "@tarekraafat/autocomplete.js": "^10.2.7", "@walletconnect/web3-provider": "^1.8.0", "assert": "^2.1.0", "bignumber.js": "^9.1.2", "bootstrap": "^4.6.0", - "chart.js": "^4.4.0", + "chart.js": "^4.4.1", "chartjs-adapter-luxon": "^1.3.1", "clipboard": "^2.0.11", - "core-js": "^3.33.3", + "core-js": "^3.35.0", "crypto-browserify": "^3.12.0", "dropzone": "^5.9.3", "eth-net-props": "^1.0.41", @@ -47,21 +47,21 @@ "luxon": "^3.4.4", "malihu-custom-scrollbar-plugin": "3.1.5", "mixpanel-browser": "^2.48.1", - "moment": "^2.29.4", + "moment": "^2.30.1", "nanomorph": "^5.4.0", "numeral": "^2.0.6", "os-browserify": "^0.3.0", "path-parser": "^6.1.0", "phoenix": "file:../../../deps/phoenix", "phoenix_html": "file:../../../deps/phoenix_html", - "photoswipe": "^5.4.2", + "photoswipe": "^5.4.3", "pikaday": "^1.8.2", "popper.js": "^1.14.7", "reduce-reducers": "^1.0.4", - "redux": "^4.2.1", + "redux": "^5.0.1", "stream-browserify": "^3.0.0", "stream-http": "^3.1.1", - "sweetalert2": "^11.10.1", + "sweetalert2": "^11.10.2", "urijs": "^1.19.11", "url": "^0.11.3", "util": "^0.12.5", @@ -71,26 +71,26 @@ "xss": "^1.0.14" }, "devDependencies": { - "@babel/core": "^7.23.3", - "@babel/preset-env": "^7.23.3", + "@babel/core": "^7.23.7", + "@babel/preset-env": "^7.23.7", "autoprefixer": "^10.4.16", "babel-loader": "^9.1.3", "copy-webpack-plugin": "^11.0.0", "css-loader": "^6.8.1", "css-minimizer-webpack-plugin": "^5.0.1", - "eslint": "^8.54.0", + "eslint": "^8.56.0", "eslint-config-standard": "^17.1.0", - "eslint-plugin-import": "^2.29.0", + "eslint-plugin-import": "^2.29.1", "eslint-plugin-node": "^11.1.0", "eslint-plugin-promise": "^6.1.1", "file-loader": "^6.2.0", "jest": "^29.7.0", "jest-environment-jsdom": "^29.7.0", "mini-css-extract-plugin": "^2.7.6", - "postcss": "^8.4.31", - "postcss-loader": "^7.3.3", - "sass": "^1.69.5", - "sass-loader": "^13.3.2", + "postcss": "^8.4.33", + "postcss-loader": "^7.3.4", + "sass": "^1.69.7", + "sass-loader": "^13.3.3", "style-loader": "^3.3.3", "webpack": "^5.89.0", "webpack-cli": "^5.1.4" @@ -116,15 +116,15 @@ } }, "node_modules/@amplitude/analytics-browser": { - "version": "2.3.5", - "resolved": "https://registry.npmjs.org/@amplitude/analytics-browser/-/analytics-browser-2.3.5.tgz", - "integrity": "sha512-KL9Yv0lXvCsWrCwwWAMB0kzTswBlTLxxyOAS//z0378ckQvszLYvQqje3K5t0AlXrG728cLvKcBFveZ/UgUWfg==", + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/@amplitude/analytics-browser/-/analytics-browser-2.3.8.tgz", + "integrity": "sha512-K+12aAVJPzAtWIi8Ok5Q5dvg7v7IF4G0cI8PW0COWo3uTyY103r45OcpgrpRVpVAr+41d1eiMo36jqOke89uPA==", "dependencies": { - "@amplitude/analytics-client-common": "^2.0.8", - "@amplitude/analytics-core": "^2.1.1", + "@amplitude/analytics-client-common": "^2.0.10", + "@amplitude/analytics-core": "^2.1.3", "@amplitude/analytics-types": "^2.3.1", - "@amplitude/plugin-page-view-tracking-browser": "^2.0.15", - "@amplitude/plugin-web-attribution-browser": "^2.0.15", + "@amplitude/plugin-page-view-tracking-browser": "^2.0.18", + "@amplitude/plugin-web-attribution-browser": "^2.0.18", "tslib": "^2.4.1" } }, @@ -134,12 +134,12 @@ "integrity": "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==" }, "node_modules/@amplitude/analytics-client-common": { - "version": "2.0.8", - "resolved": "https://registry.npmjs.org/@amplitude/analytics-client-common/-/analytics-client-common-2.0.8.tgz", - "integrity": "sha512-zKD/txmMFPfSVtT2gdZw+Tf07pZQEcPcB6X39+a+Wh8PjIIADYIeq6zL/2pn/9uwMkVz66sbKABKbq69XxPfCA==", + "version": "2.0.10", + "resolved": "https://registry.npmjs.org/@amplitude/analytics-client-common/-/analytics-client-common-2.0.10.tgz", + "integrity": "sha512-IaERGgBN3dmCGFbFd7SFTpTBguJIQzE/uDK44KEnLj0qw9wdoTxpLhoXQpqe5WKWsr46eONL9ROCJybHs4Efnw==", "dependencies": { "@amplitude/analytics-connector": "^1.4.8", - "@amplitude/analytics-core": "^2.1.1", + "@amplitude/analytics-core": "^2.1.3", "@amplitude/analytics-types": "^2.3.1", "tslib": "^2.4.1" } @@ -155,9 +155,9 @@ "integrity": "sha512-T8mOYzB9RRxckzhL0NTHwdge9xuFxXEOplC8B1Y3UX3NHa3BLh7DlBUZlCOwQgMc2nxDfnSweDL5S3bhC+W90g==" }, "node_modules/@amplitude/analytics-core": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/@amplitude/analytics-core/-/analytics-core-2.1.1.tgz", - "integrity": "sha512-2dHHiOnK7J/0Uk3gqu70JEvCKSgNBAXIUvw6u7bEHCQHBBW3ulpsVRSQomBeruyBBLKjgarwgawGs3yJrjIDkA==", + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@amplitude/analytics-core/-/analytics-core-2.1.3.tgz", + "integrity": "sha512-WHXf9g33t63jYy4a/1uOpq/zHPMfEj5N2HHgJrg7Eu7v4w3kOWtPSMPBAllzFWxC5Ay5HeR9n0hqlJG0yffQWg==", "dependencies": { "@amplitude/analytics-types": "^2.3.1", "tslib": "^2.4.1" @@ -174,11 +174,11 @@ "integrity": "sha512-yojBG20qvph0rpCJKb4i/FJa+otqLINEwv//hfzvjnCOcPPyS0YscI8oiRBM0rG7kZIDgaL9a6jPwkqK4ACmcw==" }, "node_modules/@amplitude/plugin-page-view-tracking-browser": { - "version": "2.0.15", - "resolved": "https://registry.npmjs.org/@amplitude/plugin-page-view-tracking-browser/-/plugin-page-view-tracking-browser-2.0.15.tgz", - "integrity": "sha512-iVviovZWROodoNs984dAslm3vCkMsl6bhIq5K0Tabt4ffi4ygIqlhdV8vj4Grr8u6mGtjgEzFchCkxdzb9TU1A==", + "version": "2.0.18", + "resolved": "https://registry.npmjs.org/@amplitude/plugin-page-view-tracking-browser/-/plugin-page-view-tracking-browser-2.0.18.tgz", + "integrity": "sha512-s7PkGOgrx6U06/emzM8k+KRGDuyP9Z2L4OyGdeQwJcURJjiZDVQsmKlTZ5/SeGvxHYgq/4QJYUmMSmzByiGTCA==", "dependencies": { - "@amplitude/analytics-client-common": "^2.0.8", + "@amplitude/analytics-client-common": "^2.0.10", "@amplitude/analytics-types": "^2.3.1", "tslib": "^2.4.1" } @@ -189,12 +189,12 @@ "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" }, "node_modules/@amplitude/plugin-web-attribution-browser": { - "version": "2.0.15", - "resolved": "https://registry.npmjs.org/@amplitude/plugin-web-attribution-browser/-/plugin-web-attribution-browser-2.0.15.tgz", - "integrity": "sha512-HSS6j2a40iSIKwug7ICzezXl6ag+cj7YU7cFbYiSF+cmNIVg4jPYgVCbTqq+IivOw+VW07pr0o+jz0ArL/Lyiw==", + "version": "2.0.18", + "resolved": "https://registry.npmjs.org/@amplitude/plugin-web-attribution-browser/-/plugin-web-attribution-browser-2.0.18.tgz", + "integrity": "sha512-mPlXu0fEYCCXhT6WpNJoM0FYkp+HIEm7L+KBEa2IHd3GD3+mh2AVDkZmgXLl3LKb++HY8mCiqC5/NcJ2AzTNhA==", "dependencies": { - "@amplitude/analytics-client-common": "^2.0.8", - "@amplitude/analytics-core": "^2.1.1", + "@amplitude/analytics-client-common": "^2.0.10", + "@amplitude/analytics-core": "^2.1.3", "@amplitude/analytics-types": "^2.3.1", "tslib": "^2.4.1" } @@ -229,11 +229,11 @@ } }, "node_modules/@babel/code-frame": { - "version": "7.22.13", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.13.tgz", - "integrity": "sha512-XktuhWlJ5g+3TJXc5upd9Ks1HutSArik6jf2eAjYFyIOf4ej3RN+184cZbzDvbPnuTJIUhPKKJE3cIsYTiAT3w==", + "version": "7.23.5", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.23.5.tgz", + "integrity": "sha512-CgH3s1a96LipHCmSUmYFPwY7MNx8C3avkq7i4Wl3cfa662ldtUe4VM1TPXX70pfmrlWTb6jLqTYrZyT2ZTJBgA==", "dependencies": { - "@babel/highlight": "^7.22.13", + "@babel/highlight": "^7.23.4", "chalk": "^2.4.2" }, "engines": { @@ -241,28 +241,28 @@ } }, "node_modules/@babel/compat-data": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.23.3.tgz", - "integrity": "sha512-BmR4bWbDIoFJmJ9z2cZ8Gmm2MXgEDgjdWgpKmKWUt54UGFJdlj31ECtbaDvCG/qVdG3AQ1SfpZEs01lUFbzLOQ==", + "version": "7.23.5", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.23.5.tgz", + "integrity": "sha512-uU27kfDRlhfKl+w1U6vp16IuvSLtjAxdArVXPa9BvLkrr7CYIsxH5adpHObeAGY/41+syctUWOZ140a2Rvkgjw==", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/core": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.23.3.tgz", - "integrity": "sha512-Jg+msLuNuCJDyBvFv5+OKOUjWMZgd85bKjbICd3zWrKAo+bJ49HJufi7CQE0q0uR8NGyO6xkCACScNqyjHSZew==", + "version": "7.23.7", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.23.7.tgz", + "integrity": "sha512-+UpDgowcmqe36d4NwqvKsyPMlOLNGMsfMmQ5WGCu+siCe3t3dfe9njrzGfdN4qq+bcNUt0+Vw6haRxBOycs4dw==", "dependencies": { "@ampproject/remapping": "^2.2.0", - "@babel/code-frame": "^7.22.13", - "@babel/generator": "^7.23.3", - "@babel/helper-compilation-targets": "^7.22.15", + "@babel/code-frame": "^7.23.5", + "@babel/generator": "^7.23.6", + "@babel/helper-compilation-targets": "^7.23.6", "@babel/helper-module-transforms": "^7.23.3", - "@babel/helpers": "^7.23.2", - "@babel/parser": "^7.23.3", + "@babel/helpers": "^7.23.7", + "@babel/parser": "^7.23.6", "@babel/template": "^7.22.15", - "@babel/traverse": "^7.23.3", - "@babel/types": "^7.23.3", + "@babel/traverse": "^7.23.7", + "@babel/types": "^7.23.6", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", @@ -283,11 +283,11 @@ "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==" }, "node_modules/@babel/generator": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.3.tgz", - "integrity": "sha512-keeZWAV4LU3tW0qRi19HRpabC/ilM0HRBBzf9/k8FFiG4KVpiv0FIy4hHfLfFQZNhziCTPTmd59zoyv6DNISzg==", + "version": "7.23.6", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.6.tgz", + "integrity": "sha512-qrSfCYxYQB5owCmGLbl8XRpX1ytXlpueOb0N0UmQwA073KZxejgQTzAmJezxvpwQD9uGtK2shHdi55QT+MbjIw==", "dependencies": { - "@babel/types": "^7.23.3", + "@babel/types": "^7.23.6", "@jridgewell/gen-mapping": "^0.3.2", "@jridgewell/trace-mapping": "^0.3.17", "jsesc": "^2.5.1" @@ -320,13 +320,13 @@ } }, "node_modules/@babel/helper-compilation-targets": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.22.15.tgz", - "integrity": "sha512-y6EEzULok0Qvz8yyLkCvVX+02ic+By2UdOhylwUOvOn9dvYc9mKICJuuU1n1XBI02YWsNsnrY1kc6DVbjcXbtw==", + "version": "7.23.6", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.23.6.tgz", + "integrity": "sha512-9JB548GZoQVmzrFgp8o7KxdgkTGm6xs9DW0o/Pim72UDjzr5ObUQ6ZzYPqA+g9OTS2bBQoctLJrky0RDCAWRgQ==", "dependencies": { - "@babel/compat-data": "^7.22.9", - "@babel/helper-validator-option": "^7.22.15", - "browserslist": "^4.21.9", + "@babel/compat-data": "^7.23.5", + "@babel/helper-validator-option": "^7.23.5", + "browserslist": "^4.22.2", "lru-cache": "^5.1.1", "semver": "^6.3.1" }, @@ -564,9 +564,9 @@ } }, "node_modules/@babel/helper-string-parser": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz", - "integrity": "sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw==", + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.23.4.tgz", + "integrity": "sha512-803gmbQdqwdf4olxrX4AJyFBV/RTr3rSmOj0rKwesmzlfhYNDEs+/iOcznzpNWlJlIlTJC2QfPFcHB6DlzdVLQ==", "engines": { "node": ">=6.9.0" } @@ -580,9 +580,9 @@ } }, "node_modules/@babel/helper-validator-option": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.22.15.tgz", - "integrity": "sha512-bMn7RmyFjY/mdECUbgn9eoSY4vqvacUnS9i9vGAGttgFWesO6B4CYWA7XlpbWgBt71iv/hfbPlynohStqnu5hA==", + "version": "7.23.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.23.5.tgz", + "integrity": "sha512-85ttAOMLsr53VgXkTbkx8oA6YTfT4q7/HzXSLEYmjcSTJPMPQtvq1BD79Byep5xMUYbGRzEpDsjUf3dyp54IKw==", "engines": { "node": ">=6.9.0" } @@ -602,24 +602,24 @@ } }, "node_modules/@babel/helpers": { - "version": "7.23.2", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.23.2.tgz", - "integrity": "sha512-lzchcp8SjTSVe/fPmLwtWVBFC7+Tbn8LGHDVfDp9JGxpAY5opSaEFgt8UQvrnECWOTdji2mOWMz1rOhkHscmGQ==", + "version": "7.23.7", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.23.7.tgz", + "integrity": "sha512-6AMnjCoC8wjqBzDHkuqpa7jAKwvMo4dC+lr/TFBz+ucfulO1XMpDnwWPGBNwClOKZ8h6xn5N81W/R5OrcKtCbQ==", "dependencies": { "@babel/template": "^7.22.15", - "@babel/traverse": "^7.23.2", - "@babel/types": "^7.23.0" + "@babel/traverse": "^7.23.7", + "@babel/types": "^7.23.6" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/highlight": { - "version": "7.22.13", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.13.tgz", - "integrity": "sha512-C/BaXcnnvBCmHTpz/VGZ8jgtE2aYlW4hxDhseJAWZb7gqGM/qtCK6iZUb0TyKFf7BOUsBH7Q7fkRsDRhg1XklQ==", + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.23.4.tgz", + "integrity": "sha512-acGdbYSfp2WheJoJm/EBBBLh/ID8KDc64ISZ9DYtBmC8/Q204PZJLHyzeB5qMzJ5trcOkybd78M4x2KWsUq++A==", "dependencies": { - "@babel/helper-validator-identifier": "^7.22.5", + "@babel/helper-validator-identifier": "^7.22.20", "chalk": "^2.4.2", "js-tokens": "^4.0.0" }, @@ -628,9 +628,9 @@ } }, "node_modules/@babel/parser": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.3.tgz", - "integrity": "sha512-uVsWNvlVsIninV2prNz/3lHCb+5CJ+e+IUBfbjToAHODtfGYLfCFuY4AU7TskI+dAKk+njsPiBjq1gKTvZOBaw==", + "version": "7.23.6", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.6.tgz", + "integrity": "sha512-Z2uID7YJ7oNvAI20O9X0bblw7Qqs8Q2hFy0R9tAfnfLkp5MW0UH9eUvnDSnFwKZ0AvgS1ucqR4KzvVHgnke1VQ==", "bin": { "parser": "bin/babel-parser.js" }, @@ -671,9 +671,9 @@ } }, "node_modules/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.23.3.tgz", - "integrity": "sha512-XaJak1qcityzrX0/IU5nKHb34VaibwP3saKqG6a/tppelgllOH13LUann4ZCIBcVOeE6H18K4Vx9QKkVww3z/w==", + "version": "7.23.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.23.7.tgz", + "integrity": "sha512-LlRT7HgaifEpQA1ZgLVOIJZZFVPWN5iReq/7/JixwBtwcoeVGDBD53ZV28rrsLYOZs1Y/EHhA8N/Z6aazHR8cw==", "dev": true, "dependencies": { "@babel/helper-environment-visitor": "^7.22.20", @@ -991,9 +991,9 @@ } }, "node_modules/@babel/plugin-transform-async-generator-functions": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.23.3.tgz", - "integrity": "sha512-59GsVNavGxAXCDDbakWSMJhajASb4kBCqDjqJsv+p5nKdbz7istmZ3HrX3L2LuiI80+zsOADCvooqQH3qGCucQ==", + "version": "7.23.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.23.7.tgz", + "integrity": "sha512-PdxEpL71bJp1byMG0va5gwQcXHxuEYC/BgI/e88mGTtohbZN28O5Yit0Plkkm/dBzCF/BxmbNcses1RH1T+urA==", "dev": true, "dependencies": { "@babel/helper-environment-visitor": "^7.22.20", @@ -1041,9 +1041,9 @@ } }, "node_modules/@babel/plugin-transform-block-scoping": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.23.3.tgz", - "integrity": "sha512-QPZxHrThbQia7UdvfpaRRlq/J9ciz1J4go0k+lPBXbgaNeY7IQrBj/9ceWjvMMI07/ZBzHl/F0R/2K0qH7jCVw==", + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.23.4.tgz", + "integrity": "sha512-0QqbP6B6HOh7/8iNR4CQU2Th/bbRtBp4KS9vcaZd1fZ0wSh5Fyssg0UCIHwxh+ka+pNDREbVLQnHCMHKZfPwfw==", "dev": true, "dependencies": { "@babel/helper-plugin-utils": "^7.22.5" @@ -1072,9 +1072,9 @@ } }, "node_modules/@babel/plugin-transform-class-static-block": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.23.3.tgz", - "integrity": "sha512-PENDVxdr7ZxKPyi5Ffc0LjXdnJyrJxyqF5T5YjlVg4a0VFfQHW0r8iAtRiDXkfHlu1wwcvdtnndGYIeJLSuRMQ==", + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.23.4.tgz", + "integrity": "sha512-nsWu/1M+ggti1SOALj3hfx5FXzAY06fwPJsUZD4/A5e1bWi46VUIWtD+kOX6/IdhXGsXBWllLFDSnqSCdUNydQ==", "dev": true, "dependencies": { "@babel/helper-create-class-features-plugin": "^7.22.15", @@ -1089,9 +1089,9 @@ } }, "node_modules/@babel/plugin-transform-classes": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.23.3.tgz", - "integrity": "sha512-FGEQmugvAEu2QtgtU0uTASXevfLMFfBeVCIIdcQhn/uBQsMTjBajdnAtanQlOcuihWh10PZ7+HWvc7NtBwP74w==", + "version": "7.23.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.23.5.tgz", + "integrity": "sha512-jvOTR4nicqYC9yzOHIhXG5emiFEOpappSJAl73SDSEDcybD+Puuze8Tnpb9p9qEyYup24tq891gkaygIFvWDqg==", "dev": true, "dependencies": { "@babel/helper-annotate-as-pure": "^7.22.5", @@ -1174,9 +1174,9 @@ } }, "node_modules/@babel/plugin-transform-dynamic-import": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.23.3.tgz", - "integrity": "sha512-vTG+cTGxPFou12Rj7ll+eD5yWeNl5/8xvQvF08y5Gv3v4mZQoyFf8/n9zg4q5vvCWt5jmgymfzMAldO7orBn7A==", + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.23.4.tgz", + "integrity": "sha512-V6jIbLhdJK86MaLh4Jpghi8ho5fGzt3imHOBu/x0jlBaPYqDoWz4RDXjmMOfnh+JWNaQleEAByZLV0QzBT4YQQ==", "dev": true, "dependencies": { "@babel/helper-plugin-utils": "^7.22.5", @@ -1206,9 +1206,9 @@ } }, "node_modules/@babel/plugin-transform-export-namespace-from": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.23.3.tgz", - "integrity": "sha512-yCLhW34wpJWRdTxxWtFZASJisihrfyMOTOQexhVzA78jlU+dH7Dw+zQgcPepQ5F3C6bAIiblZZ+qBggJdHiBAg==", + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.23.4.tgz", + "integrity": "sha512-GzuSBcKkx62dGzZI1WVgTWvkkz84FZO5TC5T8dl/Tht/rAla6Dg/Mz9Yhypg+ezVACf/rgDuQt3kbWEv7LdUDQ==", "dev": true, "dependencies": { "@babel/helper-plugin-utils": "^7.22.5", @@ -1222,12 +1222,13 @@ } }, "node_modules/@babel/plugin-transform-for-of": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.23.3.tgz", - "integrity": "sha512-X8jSm8X1CMwxmK878qsUGJRmbysKNbdpTv/O1/v0LuY/ZkZrng5WYiekYSdg9m09OTmDDUWeEDsTE+17WYbAZw==", + "version": "7.23.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.23.6.tgz", + "integrity": "sha512-aYH4ytZ0qSuBbpfhuofbg/e96oQ7U2w1Aw/UQmKT+1l39uEhUPoFS3fHevDc1G0OvewyDudfMKY1OulczHzWIw==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5" }, "engines": { "node": ">=6.9.0" @@ -1254,9 +1255,9 @@ } }, "node_modules/@babel/plugin-transform-json-strings": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.23.3.tgz", - "integrity": "sha512-H9Ej2OiISIZowZHaBwF0tsJOih1PftXJtE8EWqlEIwpc7LMTGq0rPOrywKLQ4nefzx8/HMR0D3JGXoMHYvhi0A==", + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.23.4.tgz", + "integrity": "sha512-81nTOqM1dMwZ/aRXQ59zVubN9wHGqk6UtqRK+/q+ciXmRy8fSolhGVvG09HHRGo4l6fr/c4ZhXUQH0uFW7PZbg==", "dev": true, "dependencies": { "@babel/helper-plugin-utils": "^7.22.5", @@ -1285,9 +1286,9 @@ } }, "node_modules/@babel/plugin-transform-logical-assignment-operators": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.23.3.tgz", - "integrity": "sha512-+pD5ZbxofyOygEp+zZAfujY2ShNCXRpDRIPOiBmTO693hhyOEteZgl876Xs9SAHPQpcV0vz8LvA/T+w8AzyX8A==", + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.23.4.tgz", + "integrity": "sha512-Mc/ALf1rmZTP4JKKEhUwiORU+vcfarFVLfcFiolKUo6sewoxSEgl36ak5t+4WamRsNr6nzjZXQjM35WsU+9vbg==", "dev": true, "dependencies": { "@babel/helper-plugin-utils": "^7.22.5", @@ -1414,9 +1415,9 @@ } }, "node_modules/@babel/plugin-transform-nullish-coalescing-operator": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.23.3.tgz", - "integrity": "sha512-xzg24Lnld4DYIdysyf07zJ1P+iIfJpxtVFOzX4g+bsJ3Ng5Le7rXx9KwqKzuyaUeRnt+I1EICwQITqc0E2PmpA==", + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.23.4.tgz", + "integrity": "sha512-jHE9EVVqHKAQx+VePv5LLGHjmHSJR76vawFPTdlxR/LVJPfOEGxREQwQfjuZEOPTwG92X3LINSh3M40Rv4zpVA==", "dev": true, "dependencies": { "@babel/helper-plugin-utils": "^7.22.5", @@ -1430,9 +1431,9 @@ } }, "node_modules/@babel/plugin-transform-numeric-separator": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.23.3.tgz", - "integrity": "sha512-s9GO7fIBi/BLsZ0v3Rftr6Oe4t0ctJ8h4CCXfPoEJwmvAPMyNrfkOOJzm6b9PX9YXcCJWWQd/sBF/N26eBiMVw==", + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.23.4.tgz", + "integrity": "sha512-mps6auzgwjRrwKEZA05cOwuDc9FAzoyFS4ZsG/8F43bTLf/TgkJg7QXOrPO1JO599iA3qgK9MXdMGOEC8O1h6Q==", "dev": true, "dependencies": { "@babel/helper-plugin-utils": "^7.22.5", @@ -1446,9 +1447,9 @@ } }, "node_modules/@babel/plugin-transform-object-rest-spread": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.23.3.tgz", - "integrity": "sha512-VxHt0ANkDmu8TANdE9Kc0rndo/ccsmfe2Cx2y5sI4hu3AukHQ5wAu4cM7j3ba8B9548ijVyclBU+nuDQftZsog==", + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.23.4.tgz", + "integrity": "sha512-9x9K1YyeQVw0iOXJlIzwm8ltobIIv7j2iLyP2jIhEbqPRQ7ScNgwQufU2I0Gq11VjyG4gI4yMXt2VFags+1N3g==", "dev": true, "dependencies": { "@babel/compat-data": "^7.23.3", @@ -1481,9 +1482,9 @@ } }, "node_modules/@babel/plugin-transform-optional-catch-binding": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.23.3.tgz", - "integrity": "sha512-LxYSb0iLjUamfm7f1D7GpiS4j0UAC8AOiehnsGAP8BEsIX8EOi3qV6bbctw8M7ZvLtcoZfZX5Z7rN9PlWk0m5A==", + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.23.4.tgz", + "integrity": "sha512-XIq8t0rJPHf6Wvmbn9nFxU6ao4c7WhghTR5WyV8SrJfUFzyxhCm4nhC+iAp3HFhbAKLfYpgzhJ6t4XCtVwqO5A==", "dev": true, "dependencies": { "@babel/helper-plugin-utils": "^7.22.5", @@ -1497,9 +1498,9 @@ } }, "node_modules/@babel/plugin-transform-optional-chaining": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.23.3.tgz", - "integrity": "sha512-zvL8vIfIUgMccIAK1lxjvNv572JHFJIKb4MWBz5OGdBQA0fB0Xluix5rmOby48exiJc987neOmP/m9Fnpkz3Tg==", + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.23.4.tgz", + "integrity": "sha512-ZU8y5zWOfjM5vZ+asjgAPwDaBjJzgufjES89Rs4Lpq63O300R/kOz30WCLo6BxxX6QVEilwSlpClnG5cZaikTA==", "dev": true, "dependencies": { "@babel/helper-plugin-utils": "^7.22.5", @@ -1545,9 +1546,9 @@ } }, "node_modules/@babel/plugin-transform-private-property-in-object": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.23.3.tgz", - "integrity": "sha512-a5m2oLNFyje2e/rGKjVfAELTVI5mbA0FeZpBnkOWWV7eSmKQ+T/XW0Vf+29ScLzSxX+rnsarvU0oie/4m6hkxA==", + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.23.4.tgz", + "integrity": "sha512-9G3K1YqTq3F4Vt88Djx1UZ79PDyj+yKRnUy7cZGSMe+a7jkwD259uKKuUzQlPkGam7R+8RJwh5z4xO27fA1o2A==", "dev": true, "dependencies": { "@babel/helper-annotate-as-pure": "^7.22.5", @@ -1779,18 +1780,18 @@ } }, "node_modules/@babel/preset-env": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.23.3.tgz", - "integrity": "sha512-ovzGc2uuyNfNAs/jyjIGxS8arOHS5FENZaNn4rtE7UdKMMkqHCvboHfcuhWLZNX5cB44QfcGNWjaevxMzzMf+Q==", + "version": "7.23.7", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.23.7.tgz", + "integrity": "sha512-SY27X/GtTz/L4UryMNJ6p4fH4nsgWbz84y9FE0bQeWJP6O5BhgVCt53CotQKHCOeXJel8VyhlhujhlltKms/CA==", "dev": true, "dependencies": { - "@babel/compat-data": "^7.23.3", - "@babel/helper-compilation-targets": "^7.22.15", + "@babel/compat-data": "^7.23.5", + "@babel/helper-compilation-targets": "^7.23.6", "@babel/helper-plugin-utils": "^7.22.5", - "@babel/helper-validator-option": "^7.22.15", + "@babel/helper-validator-option": "^7.23.5", "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.23.3", "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.23.3", - "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.23.3", + "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.23.7", "@babel/plugin-proposal-private-property-in-object": "7.21.0-placeholder-for-preset-env.2", "@babel/plugin-syntax-async-generators": "^7.8.4", "@babel/plugin-syntax-class-properties": "^7.12.13", @@ -1811,25 +1812,25 @@ "@babel/plugin-syntax-top-level-await": "^7.14.5", "@babel/plugin-syntax-unicode-sets-regex": "^7.18.6", "@babel/plugin-transform-arrow-functions": "^7.23.3", - "@babel/plugin-transform-async-generator-functions": "^7.23.3", + "@babel/plugin-transform-async-generator-functions": "^7.23.7", "@babel/plugin-transform-async-to-generator": "^7.23.3", "@babel/plugin-transform-block-scoped-functions": "^7.23.3", - "@babel/plugin-transform-block-scoping": "^7.23.3", + "@babel/plugin-transform-block-scoping": "^7.23.4", "@babel/plugin-transform-class-properties": "^7.23.3", - "@babel/plugin-transform-class-static-block": "^7.23.3", - "@babel/plugin-transform-classes": "^7.23.3", + "@babel/plugin-transform-class-static-block": "^7.23.4", + "@babel/plugin-transform-classes": "^7.23.5", "@babel/plugin-transform-computed-properties": "^7.23.3", "@babel/plugin-transform-destructuring": "^7.23.3", "@babel/plugin-transform-dotall-regex": "^7.23.3", "@babel/plugin-transform-duplicate-keys": "^7.23.3", - "@babel/plugin-transform-dynamic-import": "^7.23.3", + "@babel/plugin-transform-dynamic-import": "^7.23.4", "@babel/plugin-transform-exponentiation-operator": "^7.23.3", - "@babel/plugin-transform-export-namespace-from": "^7.23.3", - "@babel/plugin-transform-for-of": "^7.23.3", + "@babel/plugin-transform-export-namespace-from": "^7.23.4", + "@babel/plugin-transform-for-of": "^7.23.6", "@babel/plugin-transform-function-name": "^7.23.3", - "@babel/plugin-transform-json-strings": "^7.23.3", + "@babel/plugin-transform-json-strings": "^7.23.4", "@babel/plugin-transform-literals": "^7.23.3", - "@babel/plugin-transform-logical-assignment-operators": "^7.23.3", + "@babel/plugin-transform-logical-assignment-operators": "^7.23.4", "@babel/plugin-transform-member-expression-literals": "^7.23.3", "@babel/plugin-transform-modules-amd": "^7.23.3", "@babel/plugin-transform-modules-commonjs": "^7.23.3", @@ -1837,15 +1838,15 @@ "@babel/plugin-transform-modules-umd": "^7.23.3", "@babel/plugin-transform-named-capturing-groups-regex": "^7.22.5", "@babel/plugin-transform-new-target": "^7.23.3", - "@babel/plugin-transform-nullish-coalescing-operator": "^7.23.3", - "@babel/plugin-transform-numeric-separator": "^7.23.3", - "@babel/plugin-transform-object-rest-spread": "^7.23.3", + "@babel/plugin-transform-nullish-coalescing-operator": "^7.23.4", + "@babel/plugin-transform-numeric-separator": "^7.23.4", + "@babel/plugin-transform-object-rest-spread": "^7.23.4", "@babel/plugin-transform-object-super": "^7.23.3", - "@babel/plugin-transform-optional-catch-binding": "^7.23.3", - "@babel/plugin-transform-optional-chaining": "^7.23.3", + "@babel/plugin-transform-optional-catch-binding": "^7.23.4", + "@babel/plugin-transform-optional-chaining": "^7.23.4", "@babel/plugin-transform-parameters": "^7.23.3", "@babel/plugin-transform-private-methods": "^7.23.3", - "@babel/plugin-transform-private-property-in-object": "^7.23.3", + "@babel/plugin-transform-private-property-in-object": "^7.23.4", "@babel/plugin-transform-property-literals": "^7.23.3", "@babel/plugin-transform-regenerator": "^7.23.3", "@babel/plugin-transform-reserved-words": "^7.23.3", @@ -1859,9 +1860,9 @@ "@babel/plugin-transform-unicode-regex": "^7.23.3", "@babel/plugin-transform-unicode-sets-regex": "^7.23.3", "@babel/preset-modules": "0.1.6-no-external-plugins", - "babel-plugin-polyfill-corejs2": "^0.4.6", - "babel-plugin-polyfill-corejs3": "^0.8.5", - "babel-plugin-polyfill-regenerator": "^0.5.3", + "babel-plugin-polyfill-corejs2": "^0.4.7", + "babel-plugin-polyfill-corejs3": "^0.8.7", + "babel-plugin-polyfill-regenerator": "^0.5.4", "core-js-compat": "^3.31.0", "semver": "^6.3.1" }, @@ -1873,9 +1874,9 @@ } }, "node_modules/@babel/preset-env/node_modules/@babel/helper-define-polyfill-provider": { - "version": "0.4.3", - "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.4.3.tgz", - "integrity": "sha512-WBrLmuPP47n7PNwsZ57pqam6G/RGo1vw/87b0Blc53tZNGZ4x7YvZ6HgQe2vo1W/FR20OgjeZuGXzudPiXHFug==", + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.4.4.tgz", + "integrity": "sha512-QcJMILQCu2jm5TFPGA3lCpJJTeEP+mqeXooG/NZbg/h5FTFi6V0+99ahlRsW8/kRLyb24LZVCCiclDedhLKcBA==", "dev": true, "dependencies": { "@babel/helper-compilation-targets": "^7.22.6", @@ -1889,13 +1890,13 @@ } }, "node_modules/@babel/preset-env/node_modules/babel-plugin-polyfill-corejs2": { - "version": "0.4.6", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.6.tgz", - "integrity": "sha512-jhHiWVZIlnPbEUKSSNb9YoWcQGdlTLq7z1GHL4AjFxaoOUMuuEVJ+Y4pAaQUGOGk93YsVCKPbqbfw3m0SM6H8Q==", + "version": "0.4.7", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.7.tgz", + "integrity": "sha512-LidDk/tEGDfuHW2DWh/Hgo4rmnw3cduK6ZkOI1NPFceSK3n/yAGeOsNT7FLnSGHkXj3RHGSEVkN3FsCTY6w2CQ==", "dev": true, "dependencies": { "@babel/compat-data": "^7.22.6", - "@babel/helper-define-polyfill-provider": "^0.4.3", + "@babel/helper-define-polyfill-provider": "^0.4.4", "semver": "^6.3.1" }, "peerDependencies": { @@ -1903,12 +1904,12 @@ } }, "node_modules/@babel/preset-env/node_modules/babel-plugin-polyfill-regenerator": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.5.3.tgz", - "integrity": "sha512-8sHeDOmXC8csczMrYEOf0UTNa4yE2SxV5JGeT/LP1n0OYVDUUFPxG9vdk2AlDlIit4t+Kf0xCtpgXPBwnn/9pw==", + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.5.4.tgz", + "integrity": "sha512-S/x2iOCvDaCASLYsOOgWOq4bCfKYVqvO/uxjkaYyZ3rVsVE3CeAI/c84NpyuBBymEgNvHgjEot3a9/Z/kXvqsg==", "dev": true, "dependencies": { - "@babel/helper-define-polyfill-provider": "^0.4.3" + "@babel/helper-define-polyfill-provider": "^0.4.4" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" @@ -1959,19 +1960,19 @@ } }, "node_modules/@babel/traverse": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.3.tgz", - "integrity": "sha512-+K0yF1/9yR0oHdE0StHuEj3uTPzwwbrLGfNOndVJVV2TqA5+j3oljJUb4nmB954FLGjNem976+B+eDuLIjesiQ==", + "version": "7.23.7", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.7.tgz", + "integrity": "sha512-tY3mM8rH9jM0YHFGyfC0/xf+SB5eKUu7HPj7/k3fpi9dAlsMc5YbQvDi0Sh2QTPXqMhyaAtzAr807TIyfQrmyg==", "dependencies": { - "@babel/code-frame": "^7.22.13", - "@babel/generator": "^7.23.3", + "@babel/code-frame": "^7.23.5", + "@babel/generator": "^7.23.6", "@babel/helper-environment-visitor": "^7.22.20", "@babel/helper-function-name": "^7.23.0", "@babel/helper-hoist-variables": "^7.22.5", "@babel/helper-split-export-declaration": "^7.22.6", - "@babel/parser": "^7.23.3", - "@babel/types": "^7.23.3", - "debug": "^4.1.0", + "@babel/parser": "^7.23.6", + "@babel/types": "^7.23.6", + "debug": "^4.3.1", "globals": "^11.1.0" }, "engines": { @@ -1979,11 +1980,11 @@ } }, "node_modules/@babel/types": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.3.tgz", - "integrity": "sha512-OZnvoH2l8PK5eUvEcUyCt/sXgr/h+UWpVuBbOljwcrAgUl6lpchoQ++PHGyQy1AtYnVA6CEq3y5xeEI10brpXw==", + "version": "7.23.6", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.6.tgz", + "integrity": "sha512-+uarb83brBzPKN38NX1MkB6vb6+mwvR6amUulqAE7ccQw1pEl+bCia9TbdG1lsnFP7lZySvUn37CHyXQdfTwzg==", "dependencies": { - "@babel/helper-string-parser": "^7.22.5", + "@babel/helper-string-parser": "^7.23.4", "@babel/helper-validator-identifier": "^7.22.20", "to-fast-properties": "^2.0.0" }, @@ -2054,9 +2055,9 @@ } }, "node_modules/@eslint/eslintrc": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.3.tgz", - "integrity": "sha512-yZzuIG+jnVu6hNSzFEN07e8BxF3uAzYtQb6uDkaYZLo6oYZDCq454c5kB8zxnzfCYyP4MIuyBn10L0DqwujTmA==", + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", + "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", "dev": true, "dependencies": { "ajv": "^6.12.4", @@ -2122,9 +2123,9 @@ } }, "node_modules/@eslint/js": { - "version": "8.54.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.54.0.tgz", - "integrity": "sha512-ut5V+D+fOoWPgGGNj83GGjnntO39xDy6DWxO0wb7Jp3DcMX0TfIqdzHF85VTQkerdyGmuuMD9AKAo5KiNlf/AQ==", + "version": "8.56.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.56.0.tgz", + "integrity": "sha512-gMsVel9D7f2HLkBma9VbtzZRehRogVRfbr++f06nL2vnCGCNlzOD+/MUov/F4p8myyAHspEhVobgjpX64q5m6A==", "dev": true, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" @@ -2558,9 +2559,9 @@ } }, "node_modules/@fortawesome/fontawesome-free": { - "version": "6.4.2", - "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-free/-/fontawesome-free-6.4.2.tgz", - "integrity": "sha512-m5cPn3e2+FDCOgi1mz0RexTUvvQibBebOUlUlW0+YrMjDTPkiJ6VTKukA1GRsvRw+12KyJndNjj0O4AgTxm2Pg==", + "version": "6.5.1", + "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-free/-/fontawesome-free-6.5.1.tgz", + "integrity": "sha512-CNy5vSwN3fsUStPRLX7fUYojyuzoEMSXPl7zSLJ8TgtRfjv24LOnOWKT2zYwaHZCJGkdyRnTmstR0P+Ah503Gw==", "hasInstallScript": true, "engines": { "node": ">=6" @@ -4851,22 +4852,22 @@ } }, "node_modules/babel-plugin-polyfill-corejs3": { - "version": "0.8.5", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.8.5.tgz", - "integrity": "sha512-Q6CdATeAvbScWPNLB8lzSO7fgUVBkQt6zLgNlfyeCr/EQaEQR+bWiBYYPYAFyE528BMjRhL+1QBMOI4jc/c5TA==", + "version": "0.8.7", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.8.7.tgz", + "integrity": "sha512-KyDvZYxAzkC0Aj2dAPyDzi2Ym15e5JKZSK+maI7NAwSqofvuFglbSsxE7wUOvTg9oFVnHMzVzBKcqEb4PJgtOA==", "dev": true, "dependencies": { - "@babel/helper-define-polyfill-provider": "^0.4.3", - "core-js-compat": "^3.32.2" + "@babel/helper-define-polyfill-provider": "^0.4.4", + "core-js-compat": "^3.33.1" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, "node_modules/babel-plugin-polyfill-corejs3/node_modules/@babel/helper-define-polyfill-provider": { - "version": "0.4.3", - "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.4.3.tgz", - "integrity": "sha512-WBrLmuPP47n7PNwsZ57pqam6G/RGo1vw/87b0Blc53tZNGZ4x7YvZ6HgQe2vo1W/FR20OgjeZuGXzudPiXHFug==", + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.4.4.tgz", + "integrity": "sha512-QcJMILQCu2jm5TFPGA3lCpJJTeEP+mqeXooG/NZbg/h5FTFi6V0+99ahlRsW8/kRLyb24LZVCCiclDedhLKcBA==", "dev": true, "dependencies": { "@babel/helper-compilation-targets": "^7.22.6", @@ -5225,9 +5226,9 @@ ] }, "node_modules/browserslist": { - "version": "4.22.1", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.22.1.tgz", - "integrity": "sha512-FEVc202+2iuClEhZhrWy6ZiAcRLvNMyYcxZ8raemul1DYVOVdFsbqckWLdsixQZCpJlwe77Z3UTalE7jsjnKfQ==", + "version": "4.22.2", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.22.2.tgz", + "integrity": "sha512-0UgcrvQmBDvZHFGdYUehrCNIazki7/lUP3kkoi/r3YB2amZbFM9J43ZRkJTXBUZK4gmx56+Sqk9+Vs9mwZx9+A==", "funding": [ { "type": "opencollective", @@ -5243,9 +5244,9 @@ } ], "dependencies": { - "caniuse-lite": "^1.0.30001541", - "electron-to-chromium": "^1.4.535", - "node-releases": "^2.0.13", + "caniuse-lite": "^1.0.30001565", + "electron-to-chromium": "^1.4.601", + "node-releases": "^2.0.14", "update-browserslist-db": "^1.0.13" }, "bin": { @@ -5496,9 +5497,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001549", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001549.tgz", - "integrity": "sha512-qRp48dPYSCYaP+KurZLhDYdVE+yEyht/3NlmcJgVQ2VMGt6JL36ndQ/7rgspdZsJuxDPFIo/OzBT2+GmIJ53BA==", + "version": "1.0.30001568", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001568.tgz", + "integrity": "sha512-vSUkH84HontZJ88MiNrOau1EBrCqEQYgkC5gIySiDlpsm8sGVrhU7Kx4V6h0tnqaHzIHZv08HlJIwPbL4XL9+A==", "funding": [ { "type": "opencollective", @@ -5558,9 +5559,9 @@ } }, "node_modules/chart.js": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.4.0.tgz", - "integrity": "sha512-vQEj6d+z0dcsKLlQvbKIMYFHd3t8W/7L2vfJIbYcfyPcRx92CsHqECpueN8qVGNlKyDcr5wBrYAYKnfu/9Q1hQ==", + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.4.1.tgz", + "integrity": "sha512-C74QN1bxwV1v2PEujhmKjOZ7iUM4w6BWs23Md/6aOZZSlwMzeCIDGuZay++rBgChYru7/+QFeoQW0fQoP534Dg==", "dependencies": { "@kurkle/color": "^0.3.0" }, @@ -5937,9 +5938,9 @@ } }, "node_modules/core-js": { - "version": "3.33.3", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.33.3.tgz", - "integrity": "sha512-lo0kOocUlLKmm6kv/FswQL8zbkH7mVsLJ/FULClOhv8WRVmKLVcs6XPNQAzstfeJTCHMyButEwG+z1kHxHoDZw==", + "version": "3.35.0", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.35.0.tgz", + "integrity": "sha512-ntakECeqg81KqMueeGJ79Q5ZgQNR+6eaE8sxGCx62zMbAIj65q+uYvatToew3m6eAGdU4gNZwpZ34NMe4GYswg==", "hasInstallScript": true, "funding": { "type": "opencollective", @@ -5947,11 +5948,11 @@ } }, "node_modules/core-js-compat": { - "version": "3.33.0", - "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.33.0.tgz", - "integrity": "sha512-0w4LcLXsVEuNkIqwjjf9rjCoPhK8uqA4tMRh4Ge26vfLtUutshn+aRJU21I9LCJlh2QQHfisNToLjw1XEJLTWw==", + "version": "3.35.0", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.35.0.tgz", + "integrity": "sha512-5blwFAddknKeNgsjBzilkdQ0+YK8L1PfqPYq40NOYMYFSS38qj+hpTcLLWwpIwA2A5bje/x5jmVn2tzUMg9IVw==", "dependencies": { - "browserslist": "^4.22.1" + "browserslist": "^4.22.2" }, "funding": { "type": "opencollective", @@ -5976,14 +5977,14 @@ } }, "node_modules/cosmiconfig": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-8.2.0.tgz", - "integrity": "sha512-3rTMnFJA1tCOPwRxtgF4wd7Ab2qvDbL8jX+3smjIbS4HlZBagTlpERbdN7iAbWlrfxE3M8c27kTwTawQ7st+OQ==", + "version": "8.3.6", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-8.3.6.tgz", + "integrity": "sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA==", "dev": true, "dependencies": { - "import-fresh": "^3.2.1", + "import-fresh": "^3.3.0", "js-yaml": "^4.1.0", - "parse-json": "^5.0.0", + "parse-json": "^5.2.0", "path-type": "^4.0.0" }, "engines": { @@ -5991,6 +5992,14 @@ }, "funding": { "url": "https://github.com/sponsors/d-fischer" + }, + "peerDependencies": { + "typescript": ">=4.9.5" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } } }, "node_modules/cosmiconfig/node_modules/argparse": { @@ -6919,9 +6928,9 @@ "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" }, "node_modules/electron-to-chromium": { - "version": "1.4.555", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.555.tgz", - "integrity": "sha512-k1wGC7UXDTyCWcONkEMRG/w6Jvrxi+SVEU+IeqUKUKjv2lGJ1b+jf1mqrloyxVTG5WYYjNQ+F6+Cb1fGrLvNcA==" + "version": "1.4.609", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.609.tgz", + "integrity": "sha512-ihiCP7PJmjoGNuLpl7TjNA8pCQWu09vGyjlPYw1Rqww4gvNuCcmvl+44G+2QyJ6S2K4o+wbTS++Xz0YN8Q9ERw==" }, "node_modules/elliptic": { "version": "6.5.4", @@ -7306,15 +7315,15 @@ } }, "node_modules/eslint": { - "version": "8.54.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.54.0.tgz", - "integrity": "sha512-NY0DfAkM8BIZDVl6PgSa1ttZbx3xHgJzSNJKYcQglem6CppHyMhRIQkBVSSMaSRnLhig3jsDbEzOjwCVt4AmmA==", + "version": "8.56.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.56.0.tgz", + "integrity": "sha512-Go19xM6T9puCOWntie1/P997aXxFsOi37JIHRWI514Hc6ZnaHGKY9xFhrU65RT6CcBEzZoGG1e6Nq+DT04ZtZQ==", "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", - "@eslint/eslintrc": "^2.1.3", - "@eslint/js": "8.54.0", + "@eslint/eslintrc": "^2.1.4", + "@eslint/js": "8.56.0", "@humanwhocodes/config-array": "^0.11.13", "@humanwhocodes/module-importer": "^1.0.1", "@nodelib/fs.walk": "^1.2.8", @@ -7456,9 +7465,9 @@ } }, "node_modules/eslint-plugin-import": { - "version": "2.29.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.29.0.tgz", - "integrity": "sha512-QPOO5NO6Odv5lpoTkddtutccQjysJuFxoPS7fAHO+9m9udNHvTCPSAMW9zGAYj8lAIdr40I8yPCdUYrncXtrwg==", + "version": "2.29.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.29.1.tgz", + "integrity": "sha512-BbPC0cuExzhiMo4Ff1BTVwHpjjv28C5R+btTOGaCRC7UEz801up0JadwkeSk5Ued6TG34uaczuVuH6qyy5YUxw==", "dev": true, "dependencies": { "array-includes": "^3.1.7", @@ -7477,7 +7486,7 @@ "object.groupby": "^1.0.1", "object.values": "^1.1.7", "semver": "^6.3.1", - "tsconfig-paths": "^3.14.2" + "tsconfig-paths": "^3.15.0" }, "engines": { "node": ">=4" @@ -11907,9 +11916,9 @@ } }, "node_modules/jiti": { - "version": "1.18.2", - "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.18.2.tgz", - "integrity": "sha512-QAdOptna2NYiSSpv0O/BwoHBSmz4YhpzJHyi+fnMRTXFjp7B8i/YG5Z8IfusxB1ufjcD2Sre1F3R+nX3fvy7gg==", + "version": "1.21.0", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.0.tgz", + "integrity": "sha512-gFqAIbuKyyso/3G2qhiO2OM6shY6EPP/R0+mkDbyspxKazh8BXDC5FiFsUjlczgdNz/vfra0da2y+aHrusLG/Q==", "dev": true, "bin": { "jiti": "bin/jiti.js" @@ -12921,9 +12930,9 @@ "integrity": "sha512-qYvlv/exQ4+svI3UOvPUpLDF0OMX5euvUH0Ny4N5QyRyhNdgAgUrVH3iUINSzEPLvx0kbo/Bp28GJKIqvE7URw==" }, "node_modules/moment": { - "version": "2.29.4", - "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.4.tgz", - "integrity": "sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==", + "version": "2.30.1", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.30.1.tgz", + "integrity": "sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==", "engines": { "node": "*" } @@ -12983,9 +12992,9 @@ "integrity": "sha1-TzFS4JVA/eKMdvRLGbvNHVpCR40=" }, "node_modules/nanoid": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.6.tgz", - "integrity": "sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==", + "version": "3.3.7", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", + "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", "dev": true, "funding": [ { @@ -13093,9 +13102,9 @@ "dev": true }, "node_modules/node-releases": { - "version": "2.0.13", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.13.tgz", - "integrity": "sha512-uYr7J37ae/ORWdZeQ1xxMJe3NtdmqMC/JZK+geofDrkLUApKRHPd18/TxtBOJ4A0/+uUIliorNrfYV6s1b02eQ==" + "version": "2.0.14", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.14.tgz", + "integrity": "sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==" }, "node_modules/normalize-path": { "version": "3.0.0", @@ -13572,9 +13581,9 @@ "link": true }, "node_modules/photoswipe": { - "version": "5.4.2", - "resolved": "https://registry.npmjs.org/photoswipe/-/photoswipe-5.4.2.tgz", - "integrity": "sha512-z5hr36nAIPOZbHJPbCJ/mQ3+ZlizttF9za5gKXKH/us1k4KNHaRbC63K1Px5sVVKUtGb/2+ixHpKqtwl0WAwvA==", + "version": "5.4.3", + "resolved": "https://registry.npmjs.org/photoswipe/-/photoswipe-5.4.3.tgz", + "integrity": "sha512-9UC6oJBK4oXFZ5HcdlcvGkfEHsVrmE4csUdCQhEjHYb3PvPLO3PG7UhnPuOgjxwmhq5s17Un5NUdum01LgBDng==", "engines": { "node": ">= 0.12.0" } @@ -13734,9 +13743,9 @@ } }, "node_modules/postcss": { - "version": "8.4.31", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", - "integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==", + "version": "8.4.33", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.33.tgz", + "integrity": "sha512-Kkpbhhdjw2qQs2O2DGX+8m5OVqEcbB9HRBvuYM9pgrjEFUg30A9LmXNlTAUj4S9kgtGyrMbTzVjH7E+s5Re2yg==", "dev": true, "funding": [ { @@ -13753,7 +13762,7 @@ } ], "dependencies": { - "nanoid": "^3.3.6", + "nanoid": "^3.3.7", "picocolors": "^1.0.0", "source-map-js": "^1.0.2" }, @@ -13860,14 +13869,14 @@ } }, "node_modules/postcss-loader": { - "version": "7.3.3", - "resolved": "https://registry.npmjs.org/postcss-loader/-/postcss-loader-7.3.3.tgz", - "integrity": "sha512-YgO/yhtevGO/vJePCQmTxiaEwER94LABZN0ZMT4A0vsak9TpO+RvKRs7EmJ8peIlB9xfXCsS7M8LjqncsUZ5HA==", + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/postcss-loader/-/postcss-loader-7.3.4.tgz", + "integrity": "sha512-iW5WTTBSC5BfsBJ9daFMPVrLT36MrNiC6fqOZTTaHjBNX6Pfd5p+hSBqe/fEeNd7pc13QiAyGt7VdGMw4eRC4A==", "dev": true, "dependencies": { - "cosmiconfig": "^8.2.0", - "jiti": "^1.18.2", - "semver": "^7.3.8" + "cosmiconfig": "^8.3.5", + "jiti": "^1.20.0", + "semver": "^7.5.4" }, "engines": { "node": ">= 14.15.0" @@ -13882,9 +13891,9 @@ } }, "node_modules/postcss-loader/node_modules/semver": { - "version": "7.3.8", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", - "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", "dev": true, "dependencies": { "lru-cache": "^6.0.0" @@ -14800,12 +14809,9 @@ "integrity": "sha512-Mb2WZ2bJF597exiqX7owBzrqJ74DHLK3yOQjCyPAaNifRncE8OD0wFIuoMhXxTnHK07+8zZ2SJEKy/qtiyR7vw==" }, "node_modules/redux": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/redux/-/redux-4.2.1.tgz", - "integrity": "sha512-LAUYz4lc+Do8/g7aeRa8JkyDErK6ekstQaqWQrNRW//MY1TvCEpMtpTWvlQ+FPbWCx+Xixu/6SHt5N0HR+SB4w==", - "dependencies": { - "@babel/runtime": "^7.9.2" - } + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz", + "integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==" }, "node_modules/regenerate": { "version": "1.4.2", @@ -15187,9 +15193,9 @@ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, "node_modules/sass": { - "version": "1.69.5", - "resolved": "https://registry.npmjs.org/sass/-/sass-1.69.5.tgz", - "integrity": "sha512-qg2+UCJibLr2LCVOt3OlPhr/dqVHWOa9XtZf2OjbLs/T4VPSJ00udtgJxH3neXZm+QqX8B+3cU7RaLqp1iVfcQ==", + "version": "1.69.7", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.69.7.tgz", + "integrity": "sha512-rzj2soDeZ8wtE2egyLXgOOHQvaC2iosZrkF6v3EUG+tBwEvhqUCzm0VP3k9gHF9LXbSrRhT5SksoI56Iw8NPnQ==", "dev": true, "dependencies": { "chokidar": ">=3.0.0 <4.0.0", @@ -15204,9 +15210,9 @@ } }, "node_modules/sass-loader": { - "version": "13.3.2", - "resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-13.3.2.tgz", - "integrity": "sha512-CQbKl57kdEv+KDLquhC+gE3pXt74LEAzm+tzywcA0/aHZuub8wTErbjAoNI57rPUWRYRNC5WUnNl8eGJNbDdwg==", + "version": "13.3.3", + "resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-13.3.3.tgz", + "integrity": "sha512-mt5YN2F1MOZr3d/wBRcZxeFgwgkH44wVc2zohO2YF6JiOMkiXe4BYRZpSu2sO1g71mo/j16txzUhsKZlqjVGzA==", "dev": true, "dependencies": { "neo-async": "^2.6.2" @@ -16086,9 +16092,9 @@ } }, "node_modules/sweetalert2": { - "version": "11.10.1", - "resolved": "https://registry.npmjs.org/sweetalert2/-/sweetalert2-11.10.1.tgz", - "integrity": "sha512-qu145oBuFfjYr5yZW9OSdG6YmRxDf8CnkgT/sXMfrXGe+asFy2imC2vlaLQ/L/naZ/JZna1MPAY56G4qYM0VUQ==", + "version": "11.10.2", + "resolved": "https://registry.npmjs.org/sweetalert2/-/sweetalert2-11.10.2.tgz", + "integrity": "sha512-BYlIxGw6OF9Rw2z1wlnh1U+fvHHkvtg4BGyimV9nZxQRGvCBfx9uonxgwuYpJuYqCtM+2W1KOm8iMIEb/2v7Hg==", "funding": { "type": "individual", "url": "https://github.com/sponsors/limonte" @@ -16346,9 +16352,9 @@ } }, "node_modules/tsconfig-paths": { - "version": "3.14.2", - "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.14.2.tgz", - "integrity": "sha512-o/9iXgCYc5L/JxCHPe3Hvh8Q/2xm5Z+p18PESBU6Ff33695QnCHBEjcytY2q19ua7Mbl/DavtBOLq+oG0RCL+g==", + "version": "3.15.0", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz", + "integrity": "sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==", "dev": true, "dependencies": { "@types/json5": "^0.0.29", @@ -17854,15 +17860,15 @@ "dev": true }, "@amplitude/analytics-browser": { - "version": "2.3.5", - "resolved": "https://registry.npmjs.org/@amplitude/analytics-browser/-/analytics-browser-2.3.5.tgz", - "integrity": "sha512-KL9Yv0lXvCsWrCwwWAMB0kzTswBlTLxxyOAS//z0378ckQvszLYvQqje3K5t0AlXrG728cLvKcBFveZ/UgUWfg==", + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/@amplitude/analytics-browser/-/analytics-browser-2.3.8.tgz", + "integrity": "sha512-K+12aAVJPzAtWIi8Ok5Q5dvg7v7IF4G0cI8PW0COWo3uTyY103r45OcpgrpRVpVAr+41d1eiMo36jqOke89uPA==", "requires": { - "@amplitude/analytics-client-common": "^2.0.8", - "@amplitude/analytics-core": "^2.1.1", + "@amplitude/analytics-client-common": "^2.0.10", + "@amplitude/analytics-core": "^2.1.3", "@amplitude/analytics-types": "^2.3.1", - "@amplitude/plugin-page-view-tracking-browser": "^2.0.15", - "@amplitude/plugin-web-attribution-browser": "^2.0.15", + "@amplitude/plugin-page-view-tracking-browser": "^2.0.18", + "@amplitude/plugin-web-attribution-browser": "^2.0.18", "tslib": "^2.4.1" }, "dependencies": { @@ -17874,12 +17880,12 @@ } }, "@amplitude/analytics-client-common": { - "version": "2.0.8", - "resolved": "https://registry.npmjs.org/@amplitude/analytics-client-common/-/analytics-client-common-2.0.8.tgz", - "integrity": "sha512-zKD/txmMFPfSVtT2gdZw+Tf07pZQEcPcB6X39+a+Wh8PjIIADYIeq6zL/2pn/9uwMkVz66sbKABKbq69XxPfCA==", + "version": "2.0.10", + "resolved": "https://registry.npmjs.org/@amplitude/analytics-client-common/-/analytics-client-common-2.0.10.tgz", + "integrity": "sha512-IaERGgBN3dmCGFbFd7SFTpTBguJIQzE/uDK44KEnLj0qw9wdoTxpLhoXQpqe5WKWsr46eONL9ROCJybHs4Efnw==", "requires": { "@amplitude/analytics-connector": "^1.4.8", - "@amplitude/analytics-core": "^2.1.1", + "@amplitude/analytics-core": "^2.1.3", "@amplitude/analytics-types": "^2.3.1", "tslib": "^2.4.1" }, @@ -17897,9 +17903,9 @@ "integrity": "sha512-T8mOYzB9RRxckzhL0NTHwdge9xuFxXEOplC8B1Y3UX3NHa3BLh7DlBUZlCOwQgMc2nxDfnSweDL5S3bhC+W90g==" }, "@amplitude/analytics-core": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/@amplitude/analytics-core/-/analytics-core-2.1.1.tgz", - "integrity": "sha512-2dHHiOnK7J/0Uk3gqu70JEvCKSgNBAXIUvw6u7bEHCQHBBW3ulpsVRSQomBeruyBBLKjgarwgawGs3yJrjIDkA==", + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@amplitude/analytics-core/-/analytics-core-2.1.3.tgz", + "integrity": "sha512-WHXf9g33t63jYy4a/1uOpq/zHPMfEj5N2HHgJrg7Eu7v4w3kOWtPSMPBAllzFWxC5Ay5HeR9n0hqlJG0yffQWg==", "requires": { "@amplitude/analytics-types": "^2.3.1", "tslib": "^2.4.1" @@ -17918,11 +17924,11 @@ "integrity": "sha512-yojBG20qvph0rpCJKb4i/FJa+otqLINEwv//hfzvjnCOcPPyS0YscI8oiRBM0rG7kZIDgaL9a6jPwkqK4ACmcw==" }, "@amplitude/plugin-page-view-tracking-browser": { - "version": "2.0.15", - "resolved": "https://registry.npmjs.org/@amplitude/plugin-page-view-tracking-browser/-/plugin-page-view-tracking-browser-2.0.15.tgz", - "integrity": "sha512-iVviovZWROodoNs984dAslm3vCkMsl6bhIq5K0Tabt4ffi4ygIqlhdV8vj4Grr8u6mGtjgEzFchCkxdzb9TU1A==", + "version": "2.0.18", + "resolved": "https://registry.npmjs.org/@amplitude/plugin-page-view-tracking-browser/-/plugin-page-view-tracking-browser-2.0.18.tgz", + "integrity": "sha512-s7PkGOgrx6U06/emzM8k+KRGDuyP9Z2L4OyGdeQwJcURJjiZDVQsmKlTZ5/SeGvxHYgq/4QJYUmMSmzByiGTCA==", "requires": { - "@amplitude/analytics-client-common": "^2.0.8", + "@amplitude/analytics-client-common": "^2.0.10", "@amplitude/analytics-types": "^2.3.1", "tslib": "^2.4.1" }, @@ -17935,12 +17941,12 @@ } }, "@amplitude/plugin-web-attribution-browser": { - "version": "2.0.15", - "resolved": "https://registry.npmjs.org/@amplitude/plugin-web-attribution-browser/-/plugin-web-attribution-browser-2.0.15.tgz", - "integrity": "sha512-HSS6j2a40iSIKwug7ICzezXl6ag+cj7YU7cFbYiSF+cmNIVg4jPYgVCbTqq+IivOw+VW07pr0o+jz0ArL/Lyiw==", + "version": "2.0.18", + "resolved": "https://registry.npmjs.org/@amplitude/plugin-web-attribution-browser/-/plugin-web-attribution-browser-2.0.18.tgz", + "integrity": "sha512-mPlXu0fEYCCXhT6WpNJoM0FYkp+HIEm7L+KBEa2IHd3GD3+mh2AVDkZmgXLl3LKb++HY8mCiqC5/NcJ2AzTNhA==", "requires": { - "@amplitude/analytics-client-common": "^2.0.8", - "@amplitude/analytics-core": "^2.1.1", + "@amplitude/analytics-client-common": "^2.0.10", + "@amplitude/analytics-core": "^2.1.3", "@amplitude/analytics-types": "^2.3.1", "tslib": "^2.4.1" }, @@ -17973,34 +17979,34 @@ } }, "@babel/code-frame": { - "version": "7.22.13", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.13.tgz", - "integrity": "sha512-XktuhWlJ5g+3TJXc5upd9Ks1HutSArik6jf2eAjYFyIOf4ej3RN+184cZbzDvbPnuTJIUhPKKJE3cIsYTiAT3w==", + "version": "7.23.5", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.23.5.tgz", + "integrity": "sha512-CgH3s1a96LipHCmSUmYFPwY7MNx8C3avkq7i4Wl3cfa662ldtUe4VM1TPXX70pfmrlWTb6jLqTYrZyT2ZTJBgA==", "requires": { - "@babel/highlight": "^7.22.13", + "@babel/highlight": "^7.23.4", "chalk": "^2.4.2" } }, "@babel/compat-data": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.23.3.tgz", - "integrity": "sha512-BmR4bWbDIoFJmJ9z2cZ8Gmm2MXgEDgjdWgpKmKWUt54UGFJdlj31ECtbaDvCG/qVdG3AQ1SfpZEs01lUFbzLOQ==" + "version": "7.23.5", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.23.5.tgz", + "integrity": "sha512-uU27kfDRlhfKl+w1U6vp16IuvSLtjAxdArVXPa9BvLkrr7CYIsxH5adpHObeAGY/41+syctUWOZ140a2Rvkgjw==" }, "@babel/core": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.23.3.tgz", - "integrity": "sha512-Jg+msLuNuCJDyBvFv5+OKOUjWMZgd85bKjbICd3zWrKAo+bJ49HJufi7CQE0q0uR8NGyO6xkCACScNqyjHSZew==", + "version": "7.23.7", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.23.7.tgz", + "integrity": "sha512-+UpDgowcmqe36d4NwqvKsyPMlOLNGMsfMmQ5WGCu+siCe3t3dfe9njrzGfdN4qq+bcNUt0+Vw6haRxBOycs4dw==", "requires": { "@ampproject/remapping": "^2.2.0", - "@babel/code-frame": "^7.22.13", - "@babel/generator": "^7.23.3", - "@babel/helper-compilation-targets": "^7.22.15", + "@babel/code-frame": "^7.23.5", + "@babel/generator": "^7.23.6", + "@babel/helper-compilation-targets": "^7.23.6", "@babel/helper-module-transforms": "^7.23.3", - "@babel/helpers": "^7.23.2", - "@babel/parser": "^7.23.3", + "@babel/helpers": "^7.23.7", + "@babel/parser": "^7.23.6", "@babel/template": "^7.22.15", - "@babel/traverse": "^7.23.3", - "@babel/types": "^7.23.3", + "@babel/traverse": "^7.23.7", + "@babel/types": "^7.23.6", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", @@ -18016,11 +18022,11 @@ } }, "@babel/generator": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.3.tgz", - "integrity": "sha512-keeZWAV4LU3tW0qRi19HRpabC/ilM0HRBBzf9/k8FFiG4KVpiv0FIy4hHfLfFQZNhziCTPTmd59zoyv6DNISzg==", + "version": "7.23.6", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.6.tgz", + "integrity": "sha512-qrSfCYxYQB5owCmGLbl8XRpX1ytXlpueOb0N0UmQwA073KZxejgQTzAmJezxvpwQD9uGtK2shHdi55QT+MbjIw==", "requires": { - "@babel/types": "^7.23.3", + "@babel/types": "^7.23.6", "@jridgewell/gen-mapping": "^0.3.2", "@jridgewell/trace-mapping": "^0.3.17", "jsesc": "^2.5.1" @@ -18044,13 +18050,13 @@ } }, "@babel/helper-compilation-targets": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.22.15.tgz", - "integrity": "sha512-y6EEzULok0Qvz8yyLkCvVX+02ic+By2UdOhylwUOvOn9dvYc9mKICJuuU1n1XBI02YWsNsnrY1kc6DVbjcXbtw==", + "version": "7.23.6", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.23.6.tgz", + "integrity": "sha512-9JB548GZoQVmzrFgp8o7KxdgkTGm6xs9DW0o/Pim72UDjzr5ObUQ6ZzYPqA+g9OTS2bBQoctLJrky0RDCAWRgQ==", "requires": { - "@babel/compat-data": "^7.22.9", - "@babel/helper-validator-option": "^7.22.15", - "browserslist": "^4.21.9", + "@babel/compat-data": "^7.23.5", + "@babel/helper-validator-option": "^7.23.5", + "browserslist": "^4.22.2", "lru-cache": "^5.1.1", "semver": "^6.3.1" }, @@ -18224,9 +18230,9 @@ } }, "@babel/helper-string-parser": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz", - "integrity": "sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw==" + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.23.4.tgz", + "integrity": "sha512-803gmbQdqwdf4olxrX4AJyFBV/RTr3rSmOj0rKwesmzlfhYNDEs+/iOcznzpNWlJlIlTJC2QfPFcHB6DlzdVLQ==" }, "@babel/helper-validator-identifier": { "version": "7.22.20", @@ -18234,9 +18240,9 @@ "integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==" }, "@babel/helper-validator-option": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.22.15.tgz", - "integrity": "sha512-bMn7RmyFjY/mdECUbgn9eoSY4vqvacUnS9i9vGAGttgFWesO6B4CYWA7XlpbWgBt71iv/hfbPlynohStqnu5hA==" + "version": "7.23.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.23.5.tgz", + "integrity": "sha512-85ttAOMLsr53VgXkTbkx8oA6YTfT4q7/HzXSLEYmjcSTJPMPQtvq1BD79Byep5xMUYbGRzEpDsjUf3dyp54IKw==" }, "@babel/helper-wrap-function": { "version": "7.22.20", @@ -18250,29 +18256,29 @@ } }, "@babel/helpers": { - "version": "7.23.2", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.23.2.tgz", - "integrity": "sha512-lzchcp8SjTSVe/fPmLwtWVBFC7+Tbn8LGHDVfDp9JGxpAY5opSaEFgt8UQvrnECWOTdji2mOWMz1rOhkHscmGQ==", + "version": "7.23.7", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.23.7.tgz", + "integrity": "sha512-6AMnjCoC8wjqBzDHkuqpa7jAKwvMo4dC+lr/TFBz+ucfulO1XMpDnwWPGBNwClOKZ8h6xn5N81W/R5OrcKtCbQ==", "requires": { "@babel/template": "^7.22.15", - "@babel/traverse": "^7.23.2", - "@babel/types": "^7.23.0" + "@babel/traverse": "^7.23.7", + "@babel/types": "^7.23.6" } }, "@babel/highlight": { - "version": "7.22.13", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.13.tgz", - "integrity": "sha512-C/BaXcnnvBCmHTpz/VGZ8jgtE2aYlW4hxDhseJAWZb7gqGM/qtCK6iZUb0TyKFf7BOUsBH7Q7fkRsDRhg1XklQ==", + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.23.4.tgz", + "integrity": "sha512-acGdbYSfp2WheJoJm/EBBBLh/ID8KDc64ISZ9DYtBmC8/Q204PZJLHyzeB5qMzJ5trcOkybd78M4x2KWsUq++A==", "requires": { - "@babel/helper-validator-identifier": "^7.22.5", + "@babel/helper-validator-identifier": "^7.22.20", "chalk": "^2.4.2", "js-tokens": "^4.0.0" } }, "@babel/parser": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.3.tgz", - "integrity": "sha512-uVsWNvlVsIninV2prNz/3lHCb+5CJ+e+IUBfbjToAHODtfGYLfCFuY4AU7TskI+dAKk+njsPiBjq1gKTvZOBaw==" + "version": "7.23.6", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.6.tgz", + "integrity": "sha512-Z2uID7YJ7oNvAI20O9X0bblw7Qqs8Q2hFy0R9tAfnfLkp5MW0UH9eUvnDSnFwKZ0AvgS1ucqR4KzvVHgnke1VQ==" }, "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { "version": "7.23.3", @@ -18295,9 +18301,9 @@ } }, "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.23.3.tgz", - "integrity": "sha512-XaJak1qcityzrX0/IU5nKHb34VaibwP3saKqG6a/tppelgllOH13LUann4ZCIBcVOeE6H18K4Vx9QKkVww3z/w==", + "version": "7.23.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.23.7.tgz", + "integrity": "sha512-LlRT7HgaifEpQA1ZgLVOIJZZFVPWN5iReq/7/JixwBtwcoeVGDBD53ZV28rrsLYOZs1Y/EHhA8N/Z6aazHR8cw==", "dev": true, "requires": { "@babel/helper-environment-visitor": "^7.22.20", @@ -18511,9 +18517,9 @@ } }, "@babel/plugin-transform-async-generator-functions": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.23.3.tgz", - "integrity": "sha512-59GsVNavGxAXCDDbakWSMJhajASb4kBCqDjqJsv+p5nKdbz7istmZ3HrX3L2LuiI80+zsOADCvooqQH3qGCucQ==", + "version": "7.23.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.23.7.tgz", + "integrity": "sha512-PdxEpL71bJp1byMG0va5gwQcXHxuEYC/BgI/e88mGTtohbZN28O5Yit0Plkkm/dBzCF/BxmbNcses1RH1T+urA==", "dev": true, "requires": { "@babel/helper-environment-visitor": "^7.22.20", @@ -18543,9 +18549,9 @@ } }, "@babel/plugin-transform-block-scoping": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.23.3.tgz", - "integrity": "sha512-QPZxHrThbQia7UdvfpaRRlq/J9ciz1J4go0k+lPBXbgaNeY7IQrBj/9ceWjvMMI07/ZBzHl/F0R/2K0qH7jCVw==", + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.23.4.tgz", + "integrity": "sha512-0QqbP6B6HOh7/8iNR4CQU2Th/bbRtBp4KS9vcaZd1fZ0wSh5Fyssg0UCIHwxh+ka+pNDREbVLQnHCMHKZfPwfw==", "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.22.5" @@ -18562,9 +18568,9 @@ } }, "@babel/plugin-transform-class-static-block": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.23.3.tgz", - "integrity": "sha512-PENDVxdr7ZxKPyi5Ffc0LjXdnJyrJxyqF5T5YjlVg4a0VFfQHW0r8iAtRiDXkfHlu1wwcvdtnndGYIeJLSuRMQ==", + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.23.4.tgz", + "integrity": "sha512-nsWu/1M+ggti1SOALj3hfx5FXzAY06fwPJsUZD4/A5e1bWi46VUIWtD+kOX6/IdhXGsXBWllLFDSnqSCdUNydQ==", "dev": true, "requires": { "@babel/helper-create-class-features-plugin": "^7.22.15", @@ -18573,9 +18579,9 @@ } }, "@babel/plugin-transform-classes": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.23.3.tgz", - "integrity": "sha512-FGEQmugvAEu2QtgtU0uTASXevfLMFfBeVCIIdcQhn/uBQsMTjBajdnAtanQlOcuihWh10PZ7+HWvc7NtBwP74w==", + "version": "7.23.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.23.5.tgz", + "integrity": "sha512-jvOTR4nicqYC9yzOHIhXG5emiFEOpappSJAl73SDSEDcybD+Puuze8Tnpb9p9qEyYup24tq891gkaygIFvWDqg==", "dev": true, "requires": { "@babel/helper-annotate-as-pure": "^7.22.5", @@ -18628,9 +18634,9 @@ } }, "@babel/plugin-transform-dynamic-import": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.23.3.tgz", - "integrity": "sha512-vTG+cTGxPFou12Rj7ll+eD5yWeNl5/8xvQvF08y5Gv3v4mZQoyFf8/n9zg4q5vvCWt5jmgymfzMAldO7orBn7A==", + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.23.4.tgz", + "integrity": "sha512-V6jIbLhdJK86MaLh4Jpghi8ho5fGzt3imHOBu/x0jlBaPYqDoWz4RDXjmMOfnh+JWNaQleEAByZLV0QzBT4YQQ==", "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.22.5", @@ -18648,9 +18654,9 @@ } }, "@babel/plugin-transform-export-namespace-from": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.23.3.tgz", - "integrity": "sha512-yCLhW34wpJWRdTxxWtFZASJisihrfyMOTOQexhVzA78jlU+dH7Dw+zQgcPepQ5F3C6bAIiblZZ+qBggJdHiBAg==", + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.23.4.tgz", + "integrity": "sha512-GzuSBcKkx62dGzZI1WVgTWvkkz84FZO5TC5T8dl/Tht/rAla6Dg/Mz9Yhypg+ezVACf/rgDuQt3kbWEv7LdUDQ==", "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.22.5", @@ -18658,12 +18664,13 @@ } }, "@babel/plugin-transform-for-of": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.23.3.tgz", - "integrity": "sha512-X8jSm8X1CMwxmK878qsUGJRmbysKNbdpTv/O1/v0LuY/ZkZrng5WYiekYSdg9m09OTmDDUWeEDsTE+17WYbAZw==", + "version": "7.23.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.23.6.tgz", + "integrity": "sha512-aYH4ytZ0qSuBbpfhuofbg/e96oQ7U2w1Aw/UQmKT+1l39uEhUPoFS3fHevDc1G0OvewyDudfMKY1OulczHzWIw==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5" } }, "@babel/plugin-transform-function-name": { @@ -18678,9 +18685,9 @@ } }, "@babel/plugin-transform-json-strings": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.23.3.tgz", - "integrity": "sha512-H9Ej2OiISIZowZHaBwF0tsJOih1PftXJtE8EWqlEIwpc7LMTGq0rPOrywKLQ4nefzx8/HMR0D3JGXoMHYvhi0A==", + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.23.4.tgz", + "integrity": "sha512-81nTOqM1dMwZ/aRXQ59zVubN9wHGqk6UtqRK+/q+ciXmRy8fSolhGVvG09HHRGo4l6fr/c4ZhXUQH0uFW7PZbg==", "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.22.5", @@ -18697,9 +18704,9 @@ } }, "@babel/plugin-transform-logical-assignment-operators": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.23.3.tgz", - "integrity": "sha512-+pD5ZbxofyOygEp+zZAfujY2ShNCXRpDRIPOiBmTO693hhyOEteZgl876Xs9SAHPQpcV0vz8LvA/T+w8AzyX8A==", + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.23.4.tgz", + "integrity": "sha512-Mc/ALf1rmZTP4JKKEhUwiORU+vcfarFVLfcFiolKUo6sewoxSEgl36ak5t+4WamRsNr6nzjZXQjM35WsU+9vbg==", "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.22.5", @@ -18778,9 +18785,9 @@ } }, "@babel/plugin-transform-nullish-coalescing-operator": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.23.3.tgz", - "integrity": "sha512-xzg24Lnld4DYIdysyf07zJ1P+iIfJpxtVFOzX4g+bsJ3Ng5Le7rXx9KwqKzuyaUeRnt+I1EICwQITqc0E2PmpA==", + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.23.4.tgz", + "integrity": "sha512-jHE9EVVqHKAQx+VePv5LLGHjmHSJR76vawFPTdlxR/LVJPfOEGxREQwQfjuZEOPTwG92X3LINSh3M40Rv4zpVA==", "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.22.5", @@ -18788,9 +18795,9 @@ } }, "@babel/plugin-transform-numeric-separator": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.23.3.tgz", - "integrity": "sha512-s9GO7fIBi/BLsZ0v3Rftr6Oe4t0ctJ8h4CCXfPoEJwmvAPMyNrfkOOJzm6b9PX9YXcCJWWQd/sBF/N26eBiMVw==", + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.23.4.tgz", + "integrity": "sha512-mps6auzgwjRrwKEZA05cOwuDc9FAzoyFS4ZsG/8F43bTLf/TgkJg7QXOrPO1JO599iA3qgK9MXdMGOEC8O1h6Q==", "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.22.5", @@ -18798,9 +18805,9 @@ } }, "@babel/plugin-transform-object-rest-spread": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.23.3.tgz", - "integrity": "sha512-VxHt0ANkDmu8TANdE9Kc0rndo/ccsmfe2Cx2y5sI4hu3AukHQ5wAu4cM7j3ba8B9548ijVyclBU+nuDQftZsog==", + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.23.4.tgz", + "integrity": "sha512-9x9K1YyeQVw0iOXJlIzwm8ltobIIv7j2iLyP2jIhEbqPRQ7ScNgwQufU2I0Gq11VjyG4gI4yMXt2VFags+1N3g==", "dev": true, "requires": { "@babel/compat-data": "^7.23.3", @@ -18821,9 +18828,9 @@ } }, "@babel/plugin-transform-optional-catch-binding": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.23.3.tgz", - "integrity": "sha512-LxYSb0iLjUamfm7f1D7GpiS4j0UAC8AOiehnsGAP8BEsIX8EOi3qV6bbctw8M7ZvLtcoZfZX5Z7rN9PlWk0m5A==", + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.23.4.tgz", + "integrity": "sha512-XIq8t0rJPHf6Wvmbn9nFxU6ao4c7WhghTR5WyV8SrJfUFzyxhCm4nhC+iAp3HFhbAKLfYpgzhJ6t4XCtVwqO5A==", "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.22.5", @@ -18831,9 +18838,9 @@ } }, "@babel/plugin-transform-optional-chaining": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.23.3.tgz", - "integrity": "sha512-zvL8vIfIUgMccIAK1lxjvNv572JHFJIKb4MWBz5OGdBQA0fB0Xluix5rmOby48exiJc987neOmP/m9Fnpkz3Tg==", + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.23.4.tgz", + "integrity": "sha512-ZU8y5zWOfjM5vZ+asjgAPwDaBjJzgufjES89Rs4Lpq63O300R/kOz30WCLo6BxxX6QVEilwSlpClnG5cZaikTA==", "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.22.5", @@ -18861,9 +18868,9 @@ } }, "@babel/plugin-transform-private-property-in-object": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.23.3.tgz", - "integrity": "sha512-a5m2oLNFyje2e/rGKjVfAELTVI5mbA0FeZpBnkOWWV7eSmKQ+T/XW0Vf+29ScLzSxX+rnsarvU0oie/4m6hkxA==", + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.23.4.tgz", + "integrity": "sha512-9G3K1YqTq3F4Vt88Djx1UZ79PDyj+yKRnUy7cZGSMe+a7jkwD259uKKuUzQlPkGam7R+8RJwh5z4xO27fA1o2A==", "dev": true, "requires": { "@babel/helper-annotate-as-pure": "^7.22.5", @@ -19010,18 +19017,18 @@ } }, "@babel/preset-env": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.23.3.tgz", - "integrity": "sha512-ovzGc2uuyNfNAs/jyjIGxS8arOHS5FENZaNn4rtE7UdKMMkqHCvboHfcuhWLZNX5cB44QfcGNWjaevxMzzMf+Q==", + "version": "7.23.7", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.23.7.tgz", + "integrity": "sha512-SY27X/GtTz/L4UryMNJ6p4fH4nsgWbz84y9FE0bQeWJP6O5BhgVCt53CotQKHCOeXJel8VyhlhujhlltKms/CA==", "dev": true, "requires": { - "@babel/compat-data": "^7.23.3", - "@babel/helper-compilation-targets": "^7.22.15", + "@babel/compat-data": "^7.23.5", + "@babel/helper-compilation-targets": "^7.23.6", "@babel/helper-plugin-utils": "^7.22.5", - "@babel/helper-validator-option": "^7.22.15", + "@babel/helper-validator-option": "^7.23.5", "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.23.3", "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.23.3", - "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.23.3", + "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.23.7", "@babel/plugin-proposal-private-property-in-object": "7.21.0-placeholder-for-preset-env.2", "@babel/plugin-syntax-async-generators": "^7.8.4", "@babel/plugin-syntax-class-properties": "^7.12.13", @@ -19042,25 +19049,25 @@ "@babel/plugin-syntax-top-level-await": "^7.14.5", "@babel/plugin-syntax-unicode-sets-regex": "^7.18.6", "@babel/plugin-transform-arrow-functions": "^7.23.3", - "@babel/plugin-transform-async-generator-functions": "^7.23.3", + "@babel/plugin-transform-async-generator-functions": "^7.23.7", "@babel/plugin-transform-async-to-generator": "^7.23.3", "@babel/plugin-transform-block-scoped-functions": "^7.23.3", - "@babel/plugin-transform-block-scoping": "^7.23.3", + "@babel/plugin-transform-block-scoping": "^7.23.4", "@babel/plugin-transform-class-properties": "^7.23.3", - "@babel/plugin-transform-class-static-block": "^7.23.3", - "@babel/plugin-transform-classes": "^7.23.3", + "@babel/plugin-transform-class-static-block": "^7.23.4", + "@babel/plugin-transform-classes": "^7.23.5", "@babel/plugin-transform-computed-properties": "^7.23.3", "@babel/plugin-transform-destructuring": "^7.23.3", "@babel/plugin-transform-dotall-regex": "^7.23.3", "@babel/plugin-transform-duplicate-keys": "^7.23.3", - "@babel/plugin-transform-dynamic-import": "^7.23.3", + "@babel/plugin-transform-dynamic-import": "^7.23.4", "@babel/plugin-transform-exponentiation-operator": "^7.23.3", - "@babel/plugin-transform-export-namespace-from": "^7.23.3", - "@babel/plugin-transform-for-of": "^7.23.3", + "@babel/plugin-transform-export-namespace-from": "^7.23.4", + "@babel/plugin-transform-for-of": "^7.23.6", "@babel/plugin-transform-function-name": "^7.23.3", - "@babel/plugin-transform-json-strings": "^7.23.3", + "@babel/plugin-transform-json-strings": "^7.23.4", "@babel/plugin-transform-literals": "^7.23.3", - "@babel/plugin-transform-logical-assignment-operators": "^7.23.3", + "@babel/plugin-transform-logical-assignment-operators": "^7.23.4", "@babel/plugin-transform-member-expression-literals": "^7.23.3", "@babel/plugin-transform-modules-amd": "^7.23.3", "@babel/plugin-transform-modules-commonjs": "^7.23.3", @@ -19068,15 +19075,15 @@ "@babel/plugin-transform-modules-umd": "^7.23.3", "@babel/plugin-transform-named-capturing-groups-regex": "^7.22.5", "@babel/plugin-transform-new-target": "^7.23.3", - "@babel/plugin-transform-nullish-coalescing-operator": "^7.23.3", - "@babel/plugin-transform-numeric-separator": "^7.23.3", - "@babel/plugin-transform-object-rest-spread": "^7.23.3", + "@babel/plugin-transform-nullish-coalescing-operator": "^7.23.4", + "@babel/plugin-transform-numeric-separator": "^7.23.4", + "@babel/plugin-transform-object-rest-spread": "^7.23.4", "@babel/plugin-transform-object-super": "^7.23.3", - "@babel/plugin-transform-optional-catch-binding": "^7.23.3", - "@babel/plugin-transform-optional-chaining": "^7.23.3", + "@babel/plugin-transform-optional-catch-binding": "^7.23.4", + "@babel/plugin-transform-optional-chaining": "^7.23.4", "@babel/plugin-transform-parameters": "^7.23.3", "@babel/plugin-transform-private-methods": "^7.23.3", - "@babel/plugin-transform-private-property-in-object": "^7.23.3", + "@babel/plugin-transform-private-property-in-object": "^7.23.4", "@babel/plugin-transform-property-literals": "^7.23.3", "@babel/plugin-transform-regenerator": "^7.23.3", "@babel/plugin-transform-reserved-words": "^7.23.3", @@ -19090,17 +19097,17 @@ "@babel/plugin-transform-unicode-regex": "^7.23.3", "@babel/plugin-transform-unicode-sets-regex": "^7.23.3", "@babel/preset-modules": "0.1.6-no-external-plugins", - "babel-plugin-polyfill-corejs2": "^0.4.6", - "babel-plugin-polyfill-corejs3": "^0.8.5", - "babel-plugin-polyfill-regenerator": "^0.5.3", + "babel-plugin-polyfill-corejs2": "^0.4.7", + "babel-plugin-polyfill-corejs3": "^0.8.7", + "babel-plugin-polyfill-regenerator": "^0.5.4", "core-js-compat": "^3.31.0", "semver": "^6.3.1" }, "dependencies": { "@babel/helper-define-polyfill-provider": { - "version": "0.4.3", - "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.4.3.tgz", - "integrity": "sha512-WBrLmuPP47n7PNwsZ57pqam6G/RGo1vw/87b0Blc53tZNGZ4x7YvZ6HgQe2vo1W/FR20OgjeZuGXzudPiXHFug==", + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.4.4.tgz", + "integrity": "sha512-QcJMILQCu2jm5TFPGA3lCpJJTeEP+mqeXooG/NZbg/h5FTFi6V0+99ahlRsW8/kRLyb24LZVCCiclDedhLKcBA==", "dev": true, "requires": { "@babel/helper-compilation-targets": "^7.22.6", @@ -19111,23 +19118,23 @@ } }, "babel-plugin-polyfill-corejs2": { - "version": "0.4.6", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.6.tgz", - "integrity": "sha512-jhHiWVZIlnPbEUKSSNb9YoWcQGdlTLq7z1GHL4AjFxaoOUMuuEVJ+Y4pAaQUGOGk93YsVCKPbqbfw3m0SM6H8Q==", + "version": "0.4.7", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.7.tgz", + "integrity": "sha512-LidDk/tEGDfuHW2DWh/Hgo4rmnw3cduK6ZkOI1NPFceSK3n/yAGeOsNT7FLnSGHkXj3RHGSEVkN3FsCTY6w2CQ==", "dev": true, "requires": { "@babel/compat-data": "^7.22.6", - "@babel/helper-define-polyfill-provider": "^0.4.3", + "@babel/helper-define-polyfill-provider": "^0.4.4", "semver": "^6.3.1" } }, "babel-plugin-polyfill-regenerator": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.5.3.tgz", - "integrity": "sha512-8sHeDOmXC8csczMrYEOf0UTNa4yE2SxV5JGeT/LP1n0OYVDUUFPxG9vdk2AlDlIit4t+Kf0xCtpgXPBwnn/9pw==", + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.5.4.tgz", + "integrity": "sha512-S/x2iOCvDaCASLYsOOgWOq4bCfKYVqvO/uxjkaYyZ3rVsVE3CeAI/c84NpyuBBymEgNvHgjEot3a9/Z/kXvqsg==", "dev": true, "requires": { - "@babel/helper-define-polyfill-provider": "^0.4.3" + "@babel/helper-define-polyfill-provider": "^0.4.4" } } } @@ -19168,28 +19175,28 @@ } }, "@babel/traverse": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.3.tgz", - "integrity": "sha512-+K0yF1/9yR0oHdE0StHuEj3uTPzwwbrLGfNOndVJVV2TqA5+j3oljJUb4nmB954FLGjNem976+B+eDuLIjesiQ==", + "version": "7.23.7", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.7.tgz", + "integrity": "sha512-tY3mM8rH9jM0YHFGyfC0/xf+SB5eKUu7HPj7/k3fpi9dAlsMc5YbQvDi0Sh2QTPXqMhyaAtzAr807TIyfQrmyg==", "requires": { - "@babel/code-frame": "^7.22.13", - "@babel/generator": "^7.23.3", + "@babel/code-frame": "^7.23.5", + "@babel/generator": "^7.23.6", "@babel/helper-environment-visitor": "^7.22.20", "@babel/helper-function-name": "^7.23.0", "@babel/helper-hoist-variables": "^7.22.5", "@babel/helper-split-export-declaration": "^7.22.6", - "@babel/parser": "^7.23.3", - "@babel/types": "^7.23.3", - "debug": "^4.1.0", + "@babel/parser": "^7.23.6", + "@babel/types": "^7.23.6", + "debug": "^4.3.1", "globals": "^11.1.0" } }, "@babel/types": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.3.tgz", - "integrity": "sha512-OZnvoH2l8PK5eUvEcUyCt/sXgr/h+UWpVuBbOljwcrAgUl6lpchoQ++PHGyQy1AtYnVA6CEq3y5xeEI10brpXw==", + "version": "7.23.6", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.6.tgz", + "integrity": "sha512-+uarb83brBzPKN38NX1MkB6vb6+mwvR6amUulqAE7ccQw1pEl+bCia9TbdG1lsnFP7lZySvUn37CHyXQdfTwzg==", "requires": { - "@babel/helper-string-parser": "^7.22.5", + "@babel/helper-string-parser": "^7.23.4", "@babel/helper-validator-identifier": "^7.22.20", "to-fast-properties": "^2.0.0" } @@ -19245,9 +19252,9 @@ "dev": true }, "@eslint/eslintrc": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.3.tgz", - "integrity": "sha512-yZzuIG+jnVu6hNSzFEN07e8BxF3uAzYtQb6uDkaYZLo6oYZDCq454c5kB8zxnzfCYyP4MIuyBn10L0DqwujTmA==", + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", + "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", "dev": true, "requires": { "ajv": "^6.12.4", @@ -19294,9 +19301,9 @@ } }, "@eslint/js": { - "version": "8.54.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.54.0.tgz", - "integrity": "sha512-ut5V+D+fOoWPgGGNj83GGjnntO39xDy6DWxO0wb7Jp3DcMX0TfIqdzHF85VTQkerdyGmuuMD9AKAo5KiNlf/AQ==", + "version": "8.56.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.56.0.tgz", + "integrity": "sha512-gMsVel9D7f2HLkBma9VbtzZRehRogVRfbr++f06nL2vnCGCNlzOD+/MUov/F4p8myyAHspEhVobgjpX64q5m6A==", "dev": true }, "@ethereumjs/common": { @@ -19540,9 +19547,9 @@ } }, "@fortawesome/fontawesome-free": { - "version": "6.4.2", - "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-free/-/fontawesome-free-6.4.2.tgz", - "integrity": "sha512-m5cPn3e2+FDCOgi1mz0RexTUvvQibBebOUlUlW0+YrMjDTPkiJ6VTKukA1GRsvRw+12KyJndNjj0O4AgTxm2Pg==" + "version": "6.5.1", + "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-free/-/fontawesome-free-6.5.1.tgz", + "integrity": "sha512-CNy5vSwN3fsUStPRLX7fUYojyuzoEMSXPl7zSLJ8TgtRfjv24LOnOWKT2zYwaHZCJGkdyRnTmstR0P+Ah503Gw==" }, "@humanwhocodes/config-array": { "version": "0.11.13", @@ -21388,19 +21395,19 @@ } }, "babel-plugin-polyfill-corejs3": { - "version": "0.8.5", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.8.5.tgz", - "integrity": "sha512-Q6CdATeAvbScWPNLB8lzSO7fgUVBkQt6zLgNlfyeCr/EQaEQR+bWiBYYPYAFyE528BMjRhL+1QBMOI4jc/c5TA==", + "version": "0.8.7", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.8.7.tgz", + "integrity": "sha512-KyDvZYxAzkC0Aj2dAPyDzi2Ym15e5JKZSK+maI7NAwSqofvuFglbSsxE7wUOvTg9oFVnHMzVzBKcqEb4PJgtOA==", "dev": true, "requires": { - "@babel/helper-define-polyfill-provider": "^0.4.3", - "core-js-compat": "^3.32.2" + "@babel/helper-define-polyfill-provider": "^0.4.4", + "core-js-compat": "^3.33.1" }, "dependencies": { "@babel/helper-define-polyfill-provider": { - "version": "0.4.3", - "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.4.3.tgz", - "integrity": "sha512-WBrLmuPP47n7PNwsZ57pqam6G/RGo1vw/87b0Blc53tZNGZ4x7YvZ6HgQe2vo1W/FR20OgjeZuGXzudPiXHFug==", + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.4.4.tgz", + "integrity": "sha512-QcJMILQCu2jm5TFPGA3lCpJJTeEP+mqeXooG/NZbg/h5FTFi6V0+99ahlRsW8/kRLyb24LZVCCiclDedhLKcBA==", "dev": true, "requires": { "@babel/helper-compilation-targets": "^7.22.6", @@ -21684,13 +21691,13 @@ } }, "browserslist": { - "version": "4.22.1", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.22.1.tgz", - "integrity": "sha512-FEVc202+2iuClEhZhrWy6ZiAcRLvNMyYcxZ8raemul1DYVOVdFsbqckWLdsixQZCpJlwe77Z3UTalE7jsjnKfQ==", + "version": "4.22.2", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.22.2.tgz", + "integrity": "sha512-0UgcrvQmBDvZHFGdYUehrCNIazki7/lUP3kkoi/r3YB2amZbFM9J43ZRkJTXBUZK4gmx56+Sqk9+Vs9mwZx9+A==", "requires": { - "caniuse-lite": "^1.0.30001541", - "electron-to-chromium": "^1.4.535", - "node-releases": "^2.0.13", + "caniuse-lite": "^1.0.30001565", + "electron-to-chromium": "^1.4.601", + "node-releases": "^2.0.14", "update-browserslist-db": "^1.0.13" } }, @@ -21882,9 +21889,9 @@ } }, "caniuse-lite": { - "version": "1.0.30001549", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001549.tgz", - "integrity": "sha512-qRp48dPYSCYaP+KurZLhDYdVE+yEyht/3NlmcJgVQ2VMGt6JL36ndQ/7rgspdZsJuxDPFIo/OzBT2+GmIJ53BA==" + "version": "1.0.30001568", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001568.tgz", + "integrity": "sha512-vSUkH84HontZJ88MiNrOau1EBrCqEQYgkC5gIySiDlpsm8sGVrhU7Kx4V6h0tnqaHzIHZv08HlJIwPbL4XL9+A==" }, "caseless": { "version": "0.12.0", @@ -21921,9 +21928,9 @@ "dev": true }, "chart.js": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.4.0.tgz", - "integrity": "sha512-vQEj6d+z0dcsKLlQvbKIMYFHd3t8W/7L2vfJIbYcfyPcRx92CsHqECpueN8qVGNlKyDcr5wBrYAYKnfu/9Q1hQ==", + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.4.1.tgz", + "integrity": "sha512-C74QN1bxwV1v2PEujhmKjOZ7iUM4w6BWs23Md/6aOZZSlwMzeCIDGuZay++rBgChYru7/+QFeoQW0fQoP534Dg==", "requires": { "@kurkle/color": "^0.3.0" } @@ -22219,16 +22226,16 @@ } }, "core-js": { - "version": "3.33.3", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.33.3.tgz", - "integrity": "sha512-lo0kOocUlLKmm6kv/FswQL8zbkH7mVsLJ/FULClOhv8WRVmKLVcs6XPNQAzstfeJTCHMyButEwG+z1kHxHoDZw==" + "version": "3.35.0", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.35.0.tgz", + "integrity": "sha512-ntakECeqg81KqMueeGJ79Q5ZgQNR+6eaE8sxGCx62zMbAIj65q+uYvatToew3m6eAGdU4gNZwpZ34NMe4GYswg==" }, "core-js-compat": { - "version": "3.33.0", - "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.33.0.tgz", - "integrity": "sha512-0w4LcLXsVEuNkIqwjjf9rjCoPhK8uqA4tMRh4Ge26vfLtUutshn+aRJU21I9LCJlh2QQHfisNToLjw1XEJLTWw==", + "version": "3.35.0", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.35.0.tgz", + "integrity": "sha512-5blwFAddknKeNgsjBzilkdQ0+YK8L1PfqPYq40NOYMYFSS38qj+hpTcLLWwpIwA2A5bje/x5jmVn2tzUMg9IVw==", "requires": { - "browserslist": "^4.22.1" + "browserslist": "^4.22.2" } }, "core-util-is": { @@ -22246,14 +22253,14 @@ } }, "cosmiconfig": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-8.2.0.tgz", - "integrity": "sha512-3rTMnFJA1tCOPwRxtgF4wd7Ab2qvDbL8jX+3smjIbS4HlZBagTlpERbdN7iAbWlrfxE3M8c27kTwTawQ7st+OQ==", + "version": "8.3.6", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-8.3.6.tgz", + "integrity": "sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA==", "dev": true, "requires": { - "import-fresh": "^3.2.1", + "import-fresh": "^3.3.0", "js-yaml": "^4.1.0", - "parse-json": "^5.0.0", + "parse-json": "^5.2.0", "path-type": "^4.0.0" }, "dependencies": { @@ -22943,9 +22950,9 @@ "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" }, "electron-to-chromium": { - "version": "1.4.555", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.555.tgz", - "integrity": "sha512-k1wGC7UXDTyCWcONkEMRG/w6Jvrxi+SVEU+IeqUKUKjv2lGJ1b+jf1mqrloyxVTG5WYYjNQ+F6+Cb1fGrLvNcA==" + "version": "1.4.609", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.609.tgz", + "integrity": "sha512-ihiCP7PJmjoGNuLpl7TjNA8pCQWu09vGyjlPYw1Rqww4gvNuCcmvl+44G+2QyJ6S2K4o+wbTS++Xz0YN8Q9ERw==" }, "elliptic": { "version": "6.5.4", @@ -23254,15 +23261,15 @@ } }, "eslint": { - "version": "8.54.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.54.0.tgz", - "integrity": "sha512-NY0DfAkM8BIZDVl6PgSa1ttZbx3xHgJzSNJKYcQglem6CppHyMhRIQkBVSSMaSRnLhig3jsDbEzOjwCVt4AmmA==", + "version": "8.56.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.56.0.tgz", + "integrity": "sha512-Go19xM6T9puCOWntie1/P997aXxFsOi37JIHRWI514Hc6ZnaHGKY9xFhrU65RT6CcBEzZoGG1e6Nq+DT04ZtZQ==", "dev": true, "requires": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", - "@eslint/eslintrc": "^2.1.3", - "@eslint/js": "8.54.0", + "@eslint/eslintrc": "^2.1.4", + "@eslint/js": "8.56.0", "@humanwhocodes/config-array": "^0.11.13", "@humanwhocodes/module-importer": "^1.0.1", "@nodelib/fs.walk": "^1.2.8", @@ -23497,9 +23504,9 @@ } }, "eslint-plugin-import": { - "version": "2.29.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.29.0.tgz", - "integrity": "sha512-QPOO5NO6Odv5lpoTkddtutccQjysJuFxoPS7fAHO+9m9udNHvTCPSAMW9zGAYj8lAIdr40I8yPCdUYrncXtrwg==", + "version": "2.29.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.29.1.tgz", + "integrity": "sha512-BbPC0cuExzhiMo4Ff1BTVwHpjjv28C5R+btTOGaCRC7UEz801up0JadwkeSk5Ued6TG34uaczuVuH6qyy5YUxw==", "dev": true, "requires": { "array-includes": "^3.1.7", @@ -23518,7 +23525,7 @@ "object.groupby": "^1.0.1", "object.values": "^1.1.7", "semver": "^6.3.1", - "tsconfig-paths": "^3.14.2" + "tsconfig-paths": "^3.15.0" }, "dependencies": { "debug": { @@ -26751,9 +26758,9 @@ } }, "jiti": { - "version": "1.18.2", - "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.18.2.tgz", - "integrity": "sha512-QAdOptna2NYiSSpv0O/BwoHBSmz4YhpzJHyi+fnMRTXFjp7B8i/YG5Z8IfusxB1ufjcD2Sre1F3R+nX3fvy7gg==", + "version": "1.21.0", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.0.tgz", + "integrity": "sha512-gFqAIbuKyyso/3G2qhiO2OM6shY6EPP/R0+mkDbyspxKazh8BXDC5FiFsUjlczgdNz/vfra0da2y+aHrusLG/Q==", "dev": true }, "jquery": { @@ -27615,9 +27622,9 @@ "integrity": "sha512-qYvlv/exQ4+svI3UOvPUpLDF0OMX5euvUH0Ny4N5QyRyhNdgAgUrVH3iUINSzEPLvx0kbo/Bp28GJKIqvE7URw==" }, "moment": { - "version": "2.29.4", - "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.4.tgz", - "integrity": "sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==" + "version": "2.30.1", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.30.1.tgz", + "integrity": "sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==" }, "ms": { "version": "2.1.2", @@ -27673,9 +27680,9 @@ "integrity": "sha1-TzFS4JVA/eKMdvRLGbvNHVpCR40=" }, "nanoid": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.6.tgz", - "integrity": "sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==", + "version": "3.3.7", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", + "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", "dev": true }, "nanomorph": { @@ -27754,9 +27761,9 @@ "dev": true }, "node-releases": { - "version": "2.0.13", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.13.tgz", - "integrity": "sha512-uYr7J37ae/ORWdZeQ1xxMJe3NtdmqMC/JZK+geofDrkLUApKRHPd18/TxtBOJ4A0/+uUIliorNrfYV6s1b02eQ==" + "version": "2.0.14", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.14.tgz", + "integrity": "sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==" }, "normalize-path": { "version": "3.0.0", @@ -28108,9 +28115,9 @@ "version": "file:../../../deps/phoenix_html" }, "photoswipe": { - "version": "5.4.2", - "resolved": "https://registry.npmjs.org/photoswipe/-/photoswipe-5.4.2.tgz", - "integrity": "sha512-z5hr36nAIPOZbHJPbCJ/mQ3+ZlizttF9za5gKXKH/us1k4KNHaRbC63K1Px5sVVKUtGb/2+ixHpKqtwl0WAwvA==" + "version": "5.4.3", + "resolved": "https://registry.npmjs.org/photoswipe/-/photoswipe-5.4.3.tgz", + "integrity": "sha512-9UC6oJBK4oXFZ5HcdlcvGkfEHsVrmE4csUdCQhEjHYb3PvPLO3PG7UhnPuOgjxwmhq5s17Un5NUdum01LgBDng==" }, "picocolors": { "version": "1.0.0", @@ -28210,12 +28217,12 @@ "integrity": "sha512-Wb4p1J4zyFTbM+u6WuO4XstYx4Ky9Cewe4DWrel7B0w6VVICvPwdOpotjzcf6eD8TsckVnIMNONQyPIUFOUbCQ==" }, "postcss": { - "version": "8.4.31", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", - "integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==", + "version": "8.4.33", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.33.tgz", + "integrity": "sha512-Kkpbhhdjw2qQs2O2DGX+8m5OVqEcbB9HRBvuYM9pgrjEFUg30A9LmXNlTAUj4S9kgtGyrMbTzVjH7E+s5Re2yg==", "dev": true, "requires": { - "nanoid": "^3.3.6", + "nanoid": "^3.3.7", "picocolors": "^1.0.0", "source-map-js": "^1.0.2" } @@ -28281,20 +28288,20 @@ "requires": {} }, "postcss-loader": { - "version": "7.3.3", - "resolved": "https://registry.npmjs.org/postcss-loader/-/postcss-loader-7.3.3.tgz", - "integrity": "sha512-YgO/yhtevGO/vJePCQmTxiaEwER94LABZN0ZMT4A0vsak9TpO+RvKRs7EmJ8peIlB9xfXCsS7M8LjqncsUZ5HA==", + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/postcss-loader/-/postcss-loader-7.3.4.tgz", + "integrity": "sha512-iW5WTTBSC5BfsBJ9daFMPVrLT36MrNiC6fqOZTTaHjBNX6Pfd5p+hSBqe/fEeNd7pc13QiAyGt7VdGMw4eRC4A==", "dev": true, "requires": { - "cosmiconfig": "^8.2.0", - "jiti": "^1.18.2", - "semver": "^7.3.8" + "cosmiconfig": "^8.3.5", + "jiti": "^1.20.0", + "semver": "^7.5.4" }, "dependencies": { "semver": { - "version": "7.3.8", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", - "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", "dev": true, "requires": { "lru-cache": "^6.0.0" @@ -28940,12 +28947,9 @@ "integrity": "sha512-Mb2WZ2bJF597exiqX7owBzrqJ74DHLK3yOQjCyPAaNifRncE8OD0wFIuoMhXxTnHK07+8zZ2SJEKy/qtiyR7vw==" }, "redux": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/redux/-/redux-4.2.1.tgz", - "integrity": "sha512-LAUYz4lc+Do8/g7aeRa8JkyDErK6ekstQaqWQrNRW//MY1TvCEpMtpTWvlQ+FPbWCx+Xixu/6SHt5N0HR+SB4w==", - "requires": { - "@babel/runtime": "^7.9.2" - } + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz", + "integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==" }, "regenerate": { "version": "1.4.2", @@ -29239,9 +29243,9 @@ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, "sass": { - "version": "1.69.5", - "resolved": "https://registry.npmjs.org/sass/-/sass-1.69.5.tgz", - "integrity": "sha512-qg2+UCJibLr2LCVOt3OlPhr/dqVHWOa9XtZf2OjbLs/T4VPSJ00udtgJxH3neXZm+QqX8B+3cU7RaLqp1iVfcQ==", + "version": "1.69.7", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.69.7.tgz", + "integrity": "sha512-rzj2soDeZ8wtE2egyLXgOOHQvaC2iosZrkF6v3EUG+tBwEvhqUCzm0VP3k9gHF9LXbSrRhT5SksoI56Iw8NPnQ==", "dev": true, "requires": { "chokidar": ">=3.0.0 <4.0.0", @@ -29250,9 +29254,9 @@ } }, "sass-loader": { - "version": "13.3.2", - "resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-13.3.2.tgz", - "integrity": "sha512-CQbKl57kdEv+KDLquhC+gE3pXt74LEAzm+tzywcA0/aHZuub8wTErbjAoNI57rPUWRYRNC5WUnNl8eGJNbDdwg==", + "version": "13.3.3", + "resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-13.3.3.tgz", + "integrity": "sha512-mt5YN2F1MOZr3d/wBRcZxeFgwgkH44wVc2zohO2YF6JiOMkiXe4BYRZpSu2sO1g71mo/j16txzUhsKZlqjVGzA==", "dev": true, "requires": { "neo-async": "^2.6.2" @@ -29891,9 +29895,9 @@ } }, "sweetalert2": { - "version": "11.10.1", - "resolved": "https://registry.npmjs.org/sweetalert2/-/sweetalert2-11.10.1.tgz", - "integrity": "sha512-qu145oBuFfjYr5yZW9OSdG6YmRxDf8CnkgT/sXMfrXGe+asFy2imC2vlaLQ/L/naZ/JZna1MPAY56G4qYM0VUQ==" + "version": "11.10.2", + "resolved": "https://registry.npmjs.org/sweetalert2/-/sweetalert2-11.10.2.tgz", + "integrity": "sha512-BYlIxGw6OF9Rw2z1wlnh1U+fvHHkvtg4BGyimV9nZxQRGvCBfx9uonxgwuYpJuYqCtM+2W1KOm8iMIEb/2v7Hg==" }, "symbol-tree": { "version": "3.2.4", @@ -30074,9 +30078,9 @@ } }, "tsconfig-paths": { - "version": "3.14.2", - "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.14.2.tgz", - "integrity": "sha512-o/9iXgCYc5L/JxCHPe3Hvh8Q/2xm5Z+p18PESBU6Ff33695QnCHBEjcytY2q19ua7Mbl/DavtBOLq+oG0RCL+g==", + "version": "3.15.0", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz", + "integrity": "sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==", "dev": true, "requires": { "@types/json5": "^0.0.29", diff --git a/apps/block_scout_web/assets/package.json b/apps/block_scout_web/assets/package.json index 8dc4f2ca26ab..e47b66d1bc48 100644 --- a/apps/block_scout_web/assets/package.json +++ b/apps/block_scout_web/assets/package.json @@ -19,17 +19,17 @@ "eslint": "eslint js/**" }, "dependencies": { - "@fortawesome/fontawesome-free": "^6.4.2", - "@amplitude/analytics-browser": "^2.3.5", + "@fortawesome/fontawesome-free": "^6.5.1", + "@amplitude/analytics-browser": "^2.3.8", "@tarekraafat/autocomplete.js": "^10.2.7", "@walletconnect/web3-provider": "^1.8.0", "assert": "^2.1.0", "bignumber.js": "^9.1.2", "bootstrap": "^4.6.0", - "chart.js": "^4.4.0", + "chart.js": "^4.4.1", "chartjs-adapter-luxon": "^1.3.1", "clipboard": "^2.0.11", - "core-js": "^3.33.3", + "core-js": "^3.35.0", "crypto-browserify": "^3.12.0", "dropzone": "^5.9.3", "eth-net-props": "^1.0.41", @@ -59,21 +59,21 @@ "luxon": "^3.4.4", "malihu-custom-scrollbar-plugin": "3.1.5", "mixpanel-browser": "^2.48.1", - "moment": "^2.29.4", + "moment": "^2.30.1", "nanomorph": "^5.4.0", "numeral": "^2.0.6", "os-browserify": "^0.3.0", "path-parser": "^6.1.0", "phoenix": "file:../../../deps/phoenix", "phoenix_html": "file:../../../deps/phoenix_html", - "photoswipe": "^5.4.2", + "photoswipe": "^5.4.3", "pikaday": "^1.8.2", "popper.js": "^1.14.7", "reduce-reducers": "^1.0.4", - "redux": "^4.2.1", + "redux": "^5.0.1", "stream-browserify": "^3.0.0", "stream-http": "^3.1.1", - "sweetalert2": "^11.10.1", + "sweetalert2": "^11.10.2", "urijs": "^1.19.11", "url": "^0.11.3", "util": "^0.12.5", @@ -83,26 +83,26 @@ "xss": "^1.0.14" }, "devDependencies": { - "@babel/core": "^7.23.3", - "@babel/preset-env": "^7.23.3", + "@babel/core": "^7.23.7", + "@babel/preset-env": "^7.23.7", "autoprefixer": "^10.4.16", "babel-loader": "^9.1.3", "copy-webpack-plugin": "^11.0.0", "css-loader": "^6.8.1", "css-minimizer-webpack-plugin": "^5.0.1", - "eslint": "^8.54.0", + "eslint": "^8.56.0", "eslint-config-standard": "^17.1.0", - "eslint-plugin-import": "^2.29.0", + "eslint-plugin-import": "^2.29.1", "eslint-plugin-node": "^11.1.0", "eslint-plugin-promise": "^6.1.1", "file-loader": "^6.2.0", "jest": "^29.7.0", "jest-environment-jsdom": "^29.7.0", "mini-css-extract-plugin": "^2.7.6", - "postcss": "^8.4.31", - "postcss-loader": "^7.3.3", - "sass": "^1.69.5", - "sass-loader": "^13.3.2", + "postcss": "^8.4.33", + "postcss-loader": "^7.3.4", + "sass": "^1.69.7", + "sass-loader": "^13.3.3", "style-loader": "^3.3.3", "webpack": "^5.89.0", "webpack-cli": "^5.1.4" diff --git a/apps/block_scout_web/lib/block_scout_web/api_router.ex b/apps/block_scout_web/lib/block_scout_web/api_router.ex index dee973786cfe..7880bd15af05 100644 --- a/apps/block_scout_web/lib/block_scout_web/api_router.ex +++ b/apps/block_scout_web/lib/block_scout_web/api_router.ex @@ -216,6 +216,7 @@ defmodule BlockScoutWeb.ApiRouter do get("/:transaction_hash_param/logs", V2.TransactionController, :logs) get("/:transaction_hash_param/raw-trace", V2.TransactionController, :raw_trace) get("/:transaction_hash_param/state-changes", V2.TransactionController, :state_changes) + get("/:transaction_hash_param/summary", V2.TransactionController, :summary) end scope "/blocks" do diff --git a/apps/block_scout_web/lib/block_scout_web/application.ex b/apps/block_scout_web/lib/block_scout_web/application.ex index cb46a885761c..f323ef8e959d 100644 --- a/apps/block_scout_web/lib/block_scout_web/application.ex +++ b/apps/block_scout_web/lib/block_scout_web/application.ex @@ -8,7 +8,7 @@ defmodule BlockScoutWeb.Application do alias BlockScoutWeb.API.APILogger alias BlockScoutWeb.Counters.{BlocksIndexedCounter, InternalTransactionsIndexedCounter} alias BlockScoutWeb.{Endpoint, Prometheus} - alias BlockScoutWeb.{MainPageRealtimeEventHandler, RealtimeEventHandler} + alias BlockScoutWeb.{MainPageRealtimeEventHandler, RealtimeEventHandler, SmartContractRealtimeEventHandler} def start(_type, _args) do import Supervisor @@ -36,6 +36,7 @@ defmodule BlockScoutWeb.Application do {Absinthe.Subscription, Endpoint}, {MainPageRealtimeEventHandler, name: MainPageRealtimeEventHandler}, {RealtimeEventHandler, name: RealtimeEventHandler}, + {SmartContractRealtimeEventHandler, name: SmartContractRealtimeEventHandler}, {BlocksIndexedCounter, name: BlocksIndexedCounter}, {InternalTransactionsIndexedCounter, name: InternalTransactionsIndexedCounter} ] diff --git a/apps/block_scout_web/lib/block_scout_web/chain.ex b/apps/block_scout_web/lib/block_scout_web/chain.ex index c7a317956057..8bb9d4d0eb2c 100644 --- a/apps/block_scout_web/lib/block_scout_web/chain.ex +++ b/apps/block_scout_web/lib/block_scout_web/chain.ex @@ -17,6 +17,7 @@ defmodule BlockScoutWeb.Chain do import Explorer.Helper, only: [parse_integer: 1] + alias Ecto.Association.NotLoaded alias Explorer.Account.{TagAddress, TagTransaction, WatchlistAddress} alias Explorer.Chain.Block.Reward @@ -140,6 +141,34 @@ defmodule BlockScoutWeb.Chain do end end + def paging_options(%{ + "fee" => fee_string, + "value" => value_string, + "block_number" => block_number_string, + "index" => index_string, + "inserted_at" => inserted_at_string, + "hash" => hash_string + }) do + with {:ok, hash} <- string_to_transaction_hash(hash_string), + {:ok, inserted_at, _} <- DateTime.from_iso8601(inserted_at_string) do + [ + paging_options: %{ + @default_paging_options + | key: %{ + fee: decimal_parse(fee_string), + value: decimal_parse(value_string), + block_number: parse_integer(block_number_string), + index: parse_integer(index_string), + inserted_at: inserted_at, + hash: hash + } + } + ] + else + _ -> [paging_options: @default_paging_options] + end + end + def paging_options(%{ "address_hash" => address_hash_string, "tx_hash" => tx_hash_string, @@ -352,8 +381,17 @@ defmodule BlockScoutWeb.Chain do end end - def paging_options(%{"smart_contract_id" => id}) do - [paging_options: %{@default_paging_options | key: {id}}] + def paging_options(%{"smart_contract_id" => id_str} = params) do + transactions_count = parse_integer(params["tx_count"]) + coin_balance = parse_integer(params["coin_balance"]) + id = parse_integer(id_str) + + [ + paging_options: %{ + @default_paging_options + | key: %{id: id, transactions_count: transactions_count, fetched_coin_balance: coin_balance} + } + ] end def paging_options(%{"items_count" => items_count_string, "state_changes" => _}) when is_binary(items_count_string) do @@ -553,10 +591,19 @@ defmodule BlockScoutWeb.Chain do %{"block_number" => block_number} end - defp paging_params(%SmartContract{} = smart_contract) do + defp paging_params(%SmartContract{address: %NotLoaded{}} = smart_contract) do %{"smart_contract_id" => smart_contract.id} end + defp paging_params(%SmartContract{} = smart_contract) do + %{ + "smart_contract_id" => smart_contract.id, + "tx_count" => smart_contract.address.transactions_count, + "coin_balance" => + smart_contract.address.fetched_coin_balance && Wei.to(smart_contract.address.fetched_coin_balance, :wei) + } + end + defp paging_params(%Withdrawal{index: index}) do %{"index" => index} end @@ -602,7 +649,9 @@ defmodule BlockScoutWeb.Chain do %{"id" => msg_id} end - @spec paging_params_with_fiat_value(CurrentTokenBalance.t()) :: %{binary() => any} + @spec paging_params_with_fiat_value(CurrentTokenBalance.t()) :: %{ + required(String.t()) => Decimal.t() | non_neg_integer() | nil + } def paging_params_with_fiat_value(%CurrentTokenBalance{id: id, value: value} = ctb) do %{"fiat_value" => ctb.fiat_value, "value" => value, "id" => id} end diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/address_token_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/address_token_controller.ex index 8433e96be186..d130043d8700 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/address_token_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/address_token_controller.ex @@ -1,7 +1,9 @@ defmodule BlockScoutWeb.AddressTokenController do use BlockScoutWeb, :controller - import BlockScoutWeb.Chain, only: [next_page_params: 4, paging_options: 1, split_list_by_page: 1] + import BlockScoutWeb.Chain, + only: [next_page_params: 4, paging_options: 1, split_list_by_page: 1, paging_params_with_fiat_value: 1] + import BlockScoutWeb.Account.AuthController, only: [current_user: 1] import BlockScoutWeb.Models.GetAddressTags, only: [get_address_tags: 2] @@ -22,7 +24,7 @@ defmodule BlockScoutWeb.AddressTokenController do {tokens, next_page} = split_list_by_page(token_balances_plus_one) next_page_path = - case next_page_params(next_page, tokens, params, &BlockScoutWeb.Chain.paging_params_with_fiat_value/1) do + case next_page_params(next_page, tokens, params, &paging_params_with_fiat_value/1) do nil -> nil diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/address_token_transfer_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/address_token_transfer_controller.ex index 25bec31b7dd4..94f87de09348 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/address_token_transfer_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/address_token_transfer_controller.ex @@ -6,7 +6,7 @@ defmodule BlockScoutWeb.AddressTokenTransferController do alias BlockScoutWeb.{AccessHelper, Controller, TransactionView} alias Explorer.{Chain, Market} - alias Explorer.Chain.Address + alias Explorer.Chain.{Address, DenormalizationHelper} alias Indexer.Fetcher.CoinBalanceOnDemand alias Phoenix.View @@ -26,8 +26,7 @@ defmodule BlockScoutWeb.AddressTokenTransferController do [token_transfers: :token] => :optional, [token_transfers: :to_address] => :optional, [token_transfers: :from_address] => :optional, - [token_transfers: :token_contract_address] => :optional, - :block => :required + [token_transfers: :token_contract_address] => :optional } ] @@ -141,6 +140,7 @@ defmodule BlockScoutWeb.AddressTokenTransferController do {:ok, false} <- AccessHelper.restricted_access?(address_hash_string, params) do options = @transaction_necessity_by_association + |> DenormalizationHelper.extend_block_necessity(:required) |> Keyword.merge(paging_options(params)) |> Keyword.merge(current_filter(params)) diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/address_transaction_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/address_transaction_controller.ex index 639e5cc39466..05ef0c98d7fe 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/address_transaction_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/address_transaction_controller.ex @@ -20,7 +20,7 @@ defmodule BlockScoutWeb.AddressTransactionController do AddressTransactionCsvExporter } - alias Explorer.Chain.Wei + alias Explorer.Chain.{DenormalizationHelper, Transaction, Wei} alias Indexer.Fetcher.CoinBalanceOnDemand alias Phoenix.View @@ -32,7 +32,6 @@ defmodule BlockScoutWeb.AddressTransactionController do [created_contract_address: :names] => :optional, [from_address: :names] => :optional, [to_address: :names] => :optional, - :block => :optional, [created_contract_address: :smart_contract] => :optional, [from_address: :smart_contract] => :optional, [to_address: :smart_contract] => :optional @@ -50,10 +49,11 @@ defmodule BlockScoutWeb.AddressTransactionController do {:ok, false} <- AccessHelper.restricted_access?(address_hash_string, params) do options = @transaction_necessity_by_association + |> DenormalizationHelper.extend_block_necessity(:optional) |> Keyword.merge(paging_options(params)) |> Keyword.merge(current_filter(params)) - results_plus_one = Chain.address_to_transactions_with_rewards(address_hash, options) + results_plus_one = Transaction.address_to_transactions_with_rewards(address_hash, options) {results, next_page} = split_list_by_page(results_plus_one) next_page_url = diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/api/rpc/transaction_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/api/rpc/transaction_controller.ex index e14aef4b94e4..fbea15ca76f1 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/api/rpc/transaction_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/api/rpc/transaction_controller.ex @@ -4,7 +4,7 @@ defmodule BlockScoutWeb.API.RPC.TransactionController do import BlockScoutWeb.Chain, only: [paging_options: 1, next_page_params: 3, split_list_by_page: 1] alias Explorer.Chain - alias Explorer.Chain.Transaction + alias Explorer.Chain.{DenormalizationHelper, Transaction} @api_true [api?: true] @@ -75,7 +75,7 @@ defmodule BlockScoutWeb.API.RPC.TransactionController do end defp transaction_from_hash(transaction_hash) do - case Chain.hash_to_transaction(transaction_hash, necessity_by_association: %{block: :required}) do + case Chain.hash_to_transaction(transaction_hash, DenormalizationHelper.extend_block_necessity([], :required)) do {:error, :not_found} -> {:transaction, :error} {:ok, transaction} -> {:transaction, {:ok, transaction}} end diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/api/v1/gas_price_oracle_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/api/v1/gas_price_oracle_controller.ex index 7b70b4b4276f..1c99dcb17136 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/api/v1/gas_price_oracle_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/api/v1/gas_price_oracle_controller.ex @@ -30,8 +30,8 @@ defmodule BlockScoutWeb.API.V1.GasPriceOracleController do |> send_resp(status, result) end - def result(gas_prices) do - gas_prices + defp result(gas_prices) do + %{slow: gas_prices[:slow][:price], average: gas_prices[:average][:price], fast: gas_prices[:fast][:price]} |> Jason.encode!() end diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/address_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/address_controller.ex index b3008a00f5ba..3a78dcdb676c 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/address_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/address_controller.ex @@ -8,16 +8,24 @@ defmodule BlockScoutWeb.API.V2.AddressController do token_transfers_next_page_params: 3, paging_options: 1, split_list_by_page: 1, - current_filter: 1 + current_filter: 1, + paging_params_with_fiat_value: 1 ] import BlockScoutWeb.PagingHelper, - only: [delete_parameters_from_next_page_params: 1, token_transfers_types_options: 1, nft_token_types_options: 1] + only: [ + delete_parameters_from_next_page_params: 1, + token_transfers_types_options: 1, + address_transactions_sorting: 1, + nft_token_types_options: 1 + ] + + import Explorer.MicroserviceInterfaces.BENS, only: [maybe_preload_ens: 1, maybe_preload_ens_to_address: 1] alias BlockScoutWeb.AccessHelper alias BlockScoutWeb.API.V2.{BlockView, TransactionView, WithdrawalView} alias Explorer.{Chain, Market} - alias Explorer.Chain.Address + alias Explorer.Chain.{Address, Transaction} alias Explorer.Chain.Address.Counters alias Explorer.Chain.Token.Instance alias Indexer.Fetcher.{CoinBalanceOnDemand, TokenBalanceOnDemand} @@ -77,7 +85,7 @@ defmodule BlockScoutWeb.API.V2.AddressController do conn |> put_status(200) - |> render(:address, %{address: fully_preloaded_address}) + |> render(:address, %{address: fully_preloaded_address |> maybe_preload_ens_to_address()}) end end @@ -120,16 +128,23 @@ defmodule BlockScoutWeb.API.V2.AddressController do @transaction_necessity_by_association |> Keyword.merge(paging_options(params)) |> Keyword.merge(current_filter(params)) + |> Keyword.merge(address_transactions_sorting(params)) - results_plus_one = Chain.address_to_transactions_without_rewards(address_hash, options, false) + results_plus_one = Transaction.address_to_transactions_without_rewards(address_hash, options, false) {transactions, next_page} = split_list_by_page(results_plus_one) - next_page_params = next_page |> next_page_params(transactions, delete_parameters_from_next_page_params(params)) + next_page_params = + next_page + |> next_page_params( + transactions, + delete_parameters_from_next_page_params(params), + &Transaction.address_transactions_next_page_params/1 + ) conn |> put_status(200) |> put_view(TransactionView) - |> render(:transactions, %{transactions: transactions, next_page_params: next_page_params}) + |> render(:transactions, %{transactions: transactions |> maybe_preload_ens(), next_page_params: next_page_params}) end end @@ -172,7 +187,10 @@ defmodule BlockScoutWeb.API.V2.AddressController do conn |> put_status(200) |> put_view(TransactionView) - |> render(:token_transfers, %{token_transfers: token_transfers, next_page_params: next_page_params}) + |> render(:token_transfers, %{ + token_transfers: token_transfers |> maybe_preload_ens(), + next_page_params: next_page_params + }) end end @@ -201,7 +219,10 @@ defmodule BlockScoutWeb.API.V2.AddressController do conn |> put_status(200) |> put_view(TransactionView) - |> render(:token_transfers, %{token_transfers: token_transfers, next_page_params: next_page_params}) + |> render(:token_transfers, %{ + token_transfers: token_transfers |> maybe_preload_ens(), + next_page_params: next_page_params + }) end end @@ -232,7 +253,7 @@ defmodule BlockScoutWeb.API.V2.AddressController do |> put_status(200) |> put_view(TransactionView) |> render(:internal_transactions, %{ - internal_transactions: internal_transactions, + internal_transactions: internal_transactions |> maybe_preload_ens(), next_page_params: next_page_params }) end @@ -255,7 +276,7 @@ defmodule BlockScoutWeb.API.V2.AddressController do conn |> put_status(200) |> put_view(TransactionView) - |> render(:logs, %{logs: logs, next_page_params: next_page_params}) + |> render(:logs, %{logs: logs |> maybe_preload_ens(), next_page_params: next_page_params}) end end @@ -272,7 +293,7 @@ defmodule BlockScoutWeb.API.V2.AddressController do conn |> put_status(200) |> put_view(TransactionView) - |> render(:logs, %{logs: logs, next_page_params: next_page_params}) + |> render(:logs, %{logs: logs |> maybe_preload_ens(), next_page_params: next_page_params}) end end @@ -354,7 +375,7 @@ defmodule BlockScoutWeb.API.V2.AddressController do |> next_page_params( tokens, delete_parameters_from_next_page_params(params), - &BlockScoutWeb.Chain.paging_params_with_fiat_value/1 + &paging_params_with_fiat_value/1 ) conn @@ -374,7 +395,7 @@ defmodule BlockScoutWeb.API.V2.AddressController do conn |> put_status(200) |> put_view(WithdrawalView) - |> render(:withdrawals, %{withdrawals: withdrawals, next_page_params: next_page_params}) + |> render(:withdrawals, %{withdrawals: withdrawals |> maybe_preload_ens(), next_page_params: next_page_params}) end end @@ -394,7 +415,7 @@ defmodule BlockScoutWeb.API.V2.AddressController do conn |> put_status(200) |> render(:addresses, %{ - addresses: addresses, + addresses: addresses |> maybe_preload_ens(), next_page_params: next_page_params, exchange_rate: exchange_rate, total_supply: total_supply 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 a680d7616d40..49e21dcce202 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 @@ -11,6 +11,7 @@ defmodule BlockScoutWeb.API.V2.BlockController do ] import BlockScoutWeb.PagingHelper, only: [delete_parameters_from_next_page_params: 1, select_block_type: 1] + import Explorer.MicroserviceInterfaces.BENS, only: [maybe_preload_ens: 1] alias BlockScoutWeb.API.V2.{TransactionView, WithdrawalView} alias Explorer.Chain @@ -81,7 +82,7 @@ defmodule BlockScoutWeb.API.V2.BlockController do conn |> put_status(200) - |> render(:blocks, %{blocks: blocks, next_page_params: next_page_params}) + |> render(:blocks, %{blocks: blocks |> maybe_preload_ens(), next_page_params: next_page_params}) end def transactions(conn, %{"block_hash_or_number" => block_hash_or_number} = params) do @@ -103,7 +104,7 @@ defmodule BlockScoutWeb.API.V2.BlockController do conn |> put_status(200) |> put_view(TransactionView) - |> render(:transactions, %{transactions: transactions, next_page_params: next_page_params}) + |> render(:transactions, %{transactions: transactions |> maybe_preload_ens(), next_page_params: next_page_params}) end end @@ -122,7 +123,7 @@ defmodule BlockScoutWeb.API.V2.BlockController do conn |> put_status(200) |> put_view(WithdrawalView) - |> render(:withdrawals, %{withdrawals: withdrawals, next_page_params: next_page_params}) + |> render(:withdrawals, %{withdrawals: withdrawals |> maybe_preload_ens(), next_page_params: next_page_params}) end end end diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/fallback_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/fallback_controller.ex index 56e66c7d0a44..c7f1fc3692ee 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/fallback_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/fallback_controller.ex @@ -23,6 +23,10 @@ defmodule BlockScoutWeb.API.V2.FallbackController do @unauthorized "Unauthorized" @not_configured_api_key "API key not configured on the server" @wrong_api_key "Wrong API key" + @address_not_found "Address not found" + @address_is_not_smart_contract "Address is not smart-contract" + @empty_response "Empty response" + @tx_interpreter_service_disabled "Transaction Interpretation Service is not enabled" def call(conn, {:format, _params}) do Logger.error(fn -> @@ -232,4 +236,32 @@ defmodule BlockScoutWeb.API.V2.FallbackController do |> put_view(ApiView) |> render(:message, %{message: @wrong_api_key}) end + + def call(conn, {:address, {:error, :not_found}}) do + conn + |> put_status(:not_found) + |> put_view(ApiView) + |> render(:message, %{message: @address_not_found}) + end + + def call(conn, {:is_smart_contract, result}) when is_nil(result) or result == false do + conn + |> put_status(:not_found) + |> put_view(ApiView) + |> render(:message, %{message: @address_is_not_smart_contract}) + end + + def call(conn, {:is_empty_response, true}) do + conn + |> put_status(500) + |> put_view(ApiView) + |> render(:message, %{message: @empty_response}) + end + + def call(conn, {:tx_interpreter_enabled, false}) do + conn + |> put_status(404) + |> put_view(ApiView) + |> render(:message, %{message: @tx_interpreter_service_disabled}) + end end diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/main_page_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/main_page_controller.ex index ceec1582e098..0e06f6e058ca 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/main_page_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/main_page_controller.ex @@ -6,6 +6,7 @@ defmodule BlockScoutWeb.API.V2.MainPageController do alias Explorer.{Chain, Repo} import BlockScoutWeb.Account.AuthController, only: [current_user: 1] + import Explorer.MicroserviceInterfaces.BENS, only: [maybe_preload_ens: 1] @transactions_options [ necessity_by_association: %{ @@ -32,7 +33,7 @@ defmodule BlockScoutWeb.API.V2.MainPageController do conn |> put_status(200) |> put_view(BlockView) - |> render(:blocks, %{blocks: blocks}) + |> render(:blocks, %{blocks: blocks |> maybe_preload_ens()}) end def transactions(conn, _params) do @@ -41,7 +42,7 @@ defmodule BlockScoutWeb.API.V2.MainPageController do conn |> put_status(200) |> put_view(TransactionView) - |> render(:transactions, %{transactions: recent_transactions}) + |> render(:transactions, %{transactions: recent_transactions |> maybe_preload_ens()}) end def watchlist_transactions(conn, _params) do @@ -51,7 +52,10 @@ defmodule BlockScoutWeb.API.V2.MainPageController do conn |> put_status(200) |> put_view(TransactionView) - |> render(:transactions_watchlist, %{transactions: transactions, watchlist_names: watchlist_names}) + |> render(:transactions_watchlist, %{ + transactions: transactions |> maybe_preload_ens(), + watchlist_names: watchlist_names + }) end end diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/search_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/search_controller.ex index abe0aca31486..0a3c0f17aed4 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/search_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/search_controller.ex @@ -2,6 +2,7 @@ defmodule BlockScoutWeb.API.V2.SearchController do use Phoenix.Controller import BlockScoutWeb.Chain, only: [paging_options: 1, next_page_params: 3, split_list_by_page: 1, from_param: 1] + import Explorer.MicroserviceInterfaces.BENS, only: [maybe_preload_ens_info_to_search_results: 1] alias Explorer.Chain.Search alias Explorer.PagingOptions @@ -22,7 +23,10 @@ defmodule BlockScoutWeb.API.V2.SearchController do conn |> put_status(200) - |> render(:search_results, %{search_results: search_results, next_page_params: next_page_params}) + |> render(:search_results, %{ + search_results: search_results |> maybe_preload_ens_info_to_search_results(), + next_page_params: next_page_params + }) end def check_redirect(conn, %{"q" => query}) do @@ -41,6 +45,6 @@ defmodule BlockScoutWeb.API.V2.SearchController do conn |> put_status(200) - |> render(:search_results, %{search_results: search_results}) + |> render(:search_results, %{search_results: search_results |> maybe_preload_ens_info_to_search_results()}) end end diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/smart_contract_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/smart_contract_controller.ex index ab443c33571f..4986c3a19ad5 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/smart_contract_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/smart_contract_controller.ex @@ -4,7 +4,7 @@ defmodule BlockScoutWeb.API.V2.SmartContractController do import BlockScoutWeb.Chain, only: [paging_options: 1, next_page_params: 3, split_list_by_page: 1] import BlockScoutWeb.PagingHelper, - only: [current_filter: 1, delete_parameters_from_next_page_params: 1, search_query: 1] + only: [current_filter: 1, delete_parameters_from_next_page_params: 1, search_query: 1, smart_contracts_sorting: 1] import Explorer.Chain.SmartContract, only: [burn_address_hash_string: 0] import Explorer.SmartContract.Solidity.Verifier, only: [parse_boolean: 1] @@ -12,9 +12,10 @@ defmodule BlockScoutWeb.API.V2.SmartContractController do alias BlockScoutWeb.{AccessHelper, AddressView} alias Ecto.Association.NotLoaded alias Explorer.Chain - alias Explorer.Chain.SmartContract + alias Explorer.Chain.{Address, SmartContract} alias Explorer.SmartContract.{Reader, Writer} alias Explorer.SmartContract.Solidity.PublishHelper + alias Explorer.ThirdPartyIntegrations.SolidityScan @smart_contract_address_options [ necessity_by_association: %{ @@ -190,15 +191,39 @@ defmodule BlockScoutWeb.API.V2.SmartContractController do end end + @doc """ + /api/v2/smart-contracts/${address_hash_string}/solidityscan-report logic + """ + @spec solidityscan_report(Plug.Conn.t(), map()) :: + {:address, {:error, :not_found}} + | {:format_address, :error} + | {:is_empty_response, true} + | {:is_smart_contract, false | nil} + | {:restricted_access, true} + | Plug.Conn.t() + def solidityscan_report(conn, %{"address_hash" => address_hash_string} = params) do + with {:format_address, {:ok, address_hash}} <- {:format_address, Chain.string_to_address_hash(address_hash_string)}, + {:ok, false} <- AccessHelper.restricted_access?(address_hash_string, params), + {:address, {:ok, address}} <- {:address, Chain.hash_to_address(address_hash)}, + {:is_smart_contract, true} <- {:is_smart_contract, Address.is_smart_contract(address)}, + response = SolidityScan.solidityscan_request(address_hash_string), + {:is_empty_response, false} <- {:is_empty_response, is_nil(response)} do + conn + |> put_status(200) + |> json(response) + end + end + def smart_contracts_list(conn, params) do full_options = - [necessity_by_association: %{[address: :token] => :optional, [address: :names] => :optional}] + [necessity_by_association: %{[address: :token] => :optional, [address: :names] => :optional, address: :required}] |> Keyword.merge(paging_options(params)) |> Keyword.merge(current_filter(params)) |> Keyword.merge(search_query(params)) + |> Keyword.merge(smart_contracts_sorting(params)) |> Keyword.merge(@api_true) - smart_contracts_plus_one = Chain.verified_contracts(full_options) + smart_contracts_plus_one = SmartContract.verified_contracts(full_options) {smart_contracts, next_page} = split_list_by_page(smart_contracts_plus_one) next_page_params = diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/stats_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/stats_controller.ex index 825d9f1b2575..34ea7b3eae34 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/stats_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/stats_controller.ex @@ -12,6 +12,7 @@ defmodule BlockScoutWeb.API.V2.StatsController do alias Explorer.Chain.Supply.RSK alias Explorer.Chain.Transaction.History.TransactionStats alias Explorer.Counters.AverageBlockTime + alias Plug.Conn alias Timex.Duration @api_true [api?: true] @@ -39,6 +40,21 @@ defmodule BlockScoutWeb.API.V2.StatsController do nil end + coin_price_change = + case Market.fetch_recent_history() do + [today, yesterday | _] -> + today.closing_price && yesterday.closing_price && + today.closing_price + |> Decimal.div(yesterday.closing_price) + |> Decimal.sub(1) + |> Decimal.mult(100) + |> Decimal.to_float() + |> Float.ceil(2) + + _ -> + nil + end + gas_price = Application.get_env(:block_scout_web, :gas_price) json( @@ -49,16 +65,20 @@ defmodule BlockScoutWeb.API.V2.StatsController do "total_transactions" => TransactionCache.estimated_count() |> to_string(), "average_block_time" => AverageBlockTime.average_block_time() |> Duration.to_milliseconds(), "coin_price" => exchange_rate_from_db.usd_value, + "coin_price_change_percentage" => coin_price_change, "total_gas_used" => GasUsage.total() |> to_string(), "transactions_today" => Enum.at(transaction_stats, 0).number_of_transactions |> to_string(), "gas_used_today" => Enum.at(transaction_stats, 0).gas_used, "gas_prices" => gas_prices, + "gas_prices_update_in" => GasPriceOracle.global_ttl(), + "gas_price_updated_at" => GasPriceOracle.get_updated_at(), "static_gas_price" => gas_price, "market_cap" => Helper.market_cap(market_cap_type, exchange_rate_from_db), "tvl" => exchange_rate_from_db.tvl_usd, "network_utilization_percentage" => network_utilization_percentage() } |> add_rootstock_locked_btc() + |> backward_compatibility(conn) ) end @@ -135,4 +155,18 @@ defmodule BlockScoutWeb.API.V2.StatsController do _ -> stats end end + + defp backward_compatibility(response, conn) do + case Conn.get_req_header(conn, "updated-gas-oracle") do + ["true"] -> + response + + _ -> + response + |> Map.update("gas_prices", nil, fn + gas_prices -> + %{slow: gas_prices[:slow][:price], average: gas_prices[:average][:price], fast: gas_prices[:fast][:price]} + end) + end + end end diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/token_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/token_controller.ex index 14852429ab61..2e18b6649ec2 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/token_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/token_controller.ex @@ -4,7 +4,7 @@ defmodule BlockScoutWeb.API.V2.TokenController do alias BlockScoutWeb.AccessHelper alias BlockScoutWeb.API.V2.{AddressView, TransactionView} alias Explorer.{Chain, Repo} - alias Explorer.Chain.{Address, Token.Instance} + alias Explorer.Chain.{Address, Token, Token.Instance} alias Indexer.Fetcher.TokenTotalSupplyOnDemand import BlockScoutWeb.Chain, @@ -20,6 +20,8 @@ defmodule BlockScoutWeb.API.V2.TokenController do import BlockScoutWeb.PagingHelper, only: [delete_parameters_from_next_page_params: 1, token_transfers_types_options: 1, tokens_sorting: 1] + import Explorer.MicroserviceInterfaces.BENS, only: [maybe_preload_ens: 1] + action_fallback(BlockScoutWeb.API.V2.FallbackController) @api_true [api?: true] @@ -67,7 +69,10 @@ defmodule BlockScoutWeb.API.V2.TokenController do conn |> put_status(200) |> put_view(TransactionView) - |> render(:token_transfers, %{token_transfers: token_transfers, next_page_params: next_page_params}) + |> render(:token_transfers, %{ + token_transfers: token_transfers |> maybe_preload_ens(), + next_page_params: next_page_params + }) end end @@ -84,7 +89,11 @@ defmodule BlockScoutWeb.API.V2.TokenController do conn |> put_status(200) - |> render(:token_balances, %{token_balances: token_balances, next_page_params: next_page_params, token: token}) + |> render(:token_balances, %{ + token_balances: token_balances |> maybe_preload_ens(), + next_page_params: next_page_params, + token: token + }) end end @@ -132,6 +141,7 @@ defmodule BlockScoutWeb.API.V2.TokenController do results_plus_one = Chain.address_to_unique_tokens( token.contract_address_hash, + token, Keyword.merge(unique_tokens_paging_options(params), @api_true) ) @@ -154,8 +164,13 @@ defmodule BlockScoutWeb.API.V2.TokenController do {:format, {token_id, ""}} <- {:format, Integer.parse(token_id_str)} do token_instance = case Chain.erc721_or_erc1155_token_instance_from_token_id_and_token_address(token_id, address_hash, @api_true) do - {:ok, token_instance} -> token_instance |> Chain.put_owner_to_token_instance(@api_true) - {:error, :not_found} -> %{token_id: token_id, metadata: nil, owner: nil} + {:ok, token_instance} -> + token_instance + |> Chain.select_repo(@api_true).preload(:owner) + |> Chain.put_owner_to_token_instance(token, @api_true) + + {:error, :not_found} -> + %{token_id: token_id, metadata: nil, owner: nil} end conn @@ -190,7 +205,10 @@ defmodule BlockScoutWeb.API.V2.TokenController do conn |> put_status(200) |> put_view(TransactionView) - |> render(:token_transfers, %{token_transfers: token_transfers, next_page_params: next_page_params}) + |> render(:token_transfers, %{ + token_transfers: token_transfers |> maybe_preload_ens(), + next_page_params: next_page_params + }) end end @@ -217,7 +235,11 @@ defmodule BlockScoutWeb.API.V2.TokenController do conn |> put_status(200) - |> render(:token_balances, %{token_balances: token_holders, next_page_params: next_page_params, token: token}) + |> render(:token_balances, %{ + token_balances: token_holders |> maybe_preload_ens(), + next_page_params: next_page_params, + token: token + }) end end @@ -248,7 +270,7 @@ defmodule BlockScoutWeb.API.V2.TokenController do |> Keyword.merge(tokens_sorting(params)) |> Keyword.merge(@api_true) - {tokens, next_page} = filter |> Chain.list_top_tokens(options) |> split_list_by_page() + {tokens, next_page} = filter |> Token.list_top(options) |> split_list_by_page() next_page_params = next_page |> next_page_params(tokens, delete_parameters_from_next_page_params(params)) diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/transaction_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/transaction_controller.ex index 43b63b6f693d..a1e5134f4e9d 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/transaction_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/transaction_controller.ex @@ -22,7 +22,10 @@ defmodule BlockScoutWeb.API.V2.TransactionController do type_filter_options: 1 ] + import Explorer.MicroserviceInterfaces.BENS, only: [maybe_preload_ens: 1, maybe_preload_ens_to_transaction: 1] + alias BlockScoutWeb.AccessHelper + alias BlockScoutWeb.MicroserviceInterfaces.TransactionInterpretation, as: TransactionInterpretationService alias BlockScoutWeb.Models.TransactionStateHelper alias Explorer.Chain alias Explorer.Chain.Zkevm.Reader @@ -36,7 +39,6 @@ defmodule BlockScoutWeb.API.V2.TransactionController do [created_contract_address: :token] => :optional, [from_address: :names] => :optional, [to_address: :names] => :optional, - # as far as I remember this needed for substituting implementation name in `to` address instead of is's real name (in transactions) [to_address: :smart_contract] => :optional } @@ -60,7 +62,6 @@ defmodule BlockScoutWeb.API.V2.TransactionController do [created_contract_address: :names] => :optional, [from_address: :names] => :optional, [to_address: :names] => :optional, - [transaction: :block] => :optional, [created_contract_address: :smart_contract] => :optional, [from_address: :smart_contract] => :optional, [to_address: :smart_contract] => :optional @@ -95,21 +96,16 @@ defmodule BlockScoutWeb.API.V2.TransactionController do necessity_by_association_with_actions end - with {:format, {:ok, transaction_hash}} <- {:format, Chain.string_to_transaction_hash(transaction_hash_string)}, - {:not_found, {:ok, transaction}} <- - {:not_found, - Chain.hash_to_transaction( - transaction_hash, - necessity_by_association: necessity_by_association, - api?: true - )}, - {:ok, false} <- AccessHelper.restricted_access?(to_string(transaction.from_address_hash), params), - {:ok, false} <- AccessHelper.restricted_access?(to_string(transaction.to_address_hash), params), + with {:ok, transaction, _transaction_hash} <- + validate_transaction(transaction_hash_string, params, + necessity_by_association: necessity_by_association, + api?: true + ), preloaded <- Chain.preload_token_transfers(transaction, @token_transfers_in_tx_necessity_by_association, @api_true, false) do conn |> put_status(200) - |> render(:transaction, %{transaction: preloaded}) + |> render(:transaction, %{transaction: preloaded |> maybe_preload_ens_to_transaction()}) end end @@ -137,7 +133,7 @@ defmodule BlockScoutWeb.API.V2.TransactionController do conn |> put_status(200) - |> render(:transactions, %{transactions: transactions, next_page_params: next_page_params}) + |> render(:transactions, %{transactions: transactions |> maybe_preload_ens(), next_page_params: next_page_params}) end @doc """ @@ -154,7 +150,7 @@ defmodule BlockScoutWeb.API.V2.TransactionController do conn |> put_status(200) - |> render(:transactions, %{transactions: transactions, items: true}) + |> render(:transactions, %{transactions: transactions |> maybe_preload_ens(), items: true}) end def execution_node(conn, %{"execution_node_hash_param" => execution_node_hash_string} = params) do @@ -174,7 +170,7 @@ defmodule BlockScoutWeb.API.V2.TransactionController do conn |> put_status(200) - |> render(:transactions, %{transactions: transactions, next_page_params: next_page_params}) + |> render(:transactions, %{transactions: transactions |> maybe_preload_ens(), next_page_params: next_page_params}) end end @@ -183,11 +179,7 @@ defmodule BlockScoutWeb.API.V2.TransactionController do """ @spec raw_trace(Plug.Conn.t(), map()) :: Plug.Conn.t() | {atom(), any()} def raw_trace(conn, %{"transaction_hash_param" => transaction_hash_string} = params) do - with {:format, {:ok, transaction_hash}} <- {:format, Chain.string_to_transaction_hash(transaction_hash_string)}, - {:not_found, {:ok, transaction}} <- - {:not_found, Chain.hash_to_transaction(transaction_hash, @api_true)}, - {:ok, false} <- AccessHelper.restricted_access?(to_string(transaction.from_address_hash), params), - {:ok, false} <- AccessHelper.restricted_access?(to_string(transaction.to_address_hash), params) do + with {:ok, transaction, transaction_hash} <- validate_transaction(transaction_hash_string, params) do if is_nil(transaction.block_number) do conn |> put_status(200) @@ -216,11 +208,7 @@ defmodule BlockScoutWeb.API.V2.TransactionController do """ @spec token_transfers(Plug.Conn.t(), map()) :: Plug.Conn.t() | {atom(), any()} def token_transfers(conn, %{"transaction_hash_param" => transaction_hash_string} = params) do - with {:format, {:ok, transaction_hash}} <- {:format, Chain.string_to_transaction_hash(transaction_hash_string)}, - {:not_found, {:ok, transaction}} <- - {:not_found, Chain.hash_to_transaction(transaction_hash, @api_true)}, - {:ok, false} <- AccessHelper.restricted_access?(to_string(transaction.from_address_hash), params), - {:ok, false} <- AccessHelper.restricted_access?(to_string(transaction.to_address_hash), params) do + with {:ok, _transaction, transaction_hash} <- validate_transaction(transaction_hash_string, params) do paging_options = paging_options(params) full_options = @@ -243,7 +231,10 @@ defmodule BlockScoutWeb.API.V2.TransactionController do conn |> put_status(200) - |> render(:token_transfers, %{token_transfers: token_transfers, next_page_params: next_page_params}) + |> render(:token_transfers, %{ + token_transfers: token_transfers |> maybe_preload_ens(), + next_page_params: next_page_params + }) end end @@ -252,11 +243,7 @@ defmodule BlockScoutWeb.API.V2.TransactionController do """ @spec internal_transactions(Plug.Conn.t(), map()) :: Plug.Conn.t() | {atom(), any()} def internal_transactions(conn, %{"transaction_hash_param" => transaction_hash_string} = params) do - with {:format, {:ok, transaction_hash}} <- {:format, Chain.string_to_transaction_hash(transaction_hash_string)}, - {:not_found, {:ok, transaction}} <- - {:not_found, Chain.hash_to_transaction(transaction_hash, @api_true)}, - {:ok, false} <- AccessHelper.restricted_access?(to_string(transaction.from_address_hash), params), - {:ok, false} <- AccessHelper.restricted_access?(to_string(transaction.to_address_hash), params) do + with {:ok, _transaction, transaction_hash} <- validate_transaction(transaction_hash_string, params) do full_options = @internal_transaction_necessity_by_association |> Keyword.merge(paging_options(params)) @@ -273,7 +260,7 @@ defmodule BlockScoutWeb.API.V2.TransactionController do conn |> put_status(200) |> render(:internal_transactions, %{ - internal_transactions: internal_transactions, + internal_transactions: internal_transactions |> maybe_preload_ens(), next_page_params: next_page_params }) end @@ -284,11 +271,7 @@ defmodule BlockScoutWeb.API.V2.TransactionController do """ @spec logs(Plug.Conn.t(), map()) :: Plug.Conn.t() | {atom(), any()} def logs(conn, %{"transaction_hash_param" => transaction_hash_string} = params) do - with {:format, {:ok, transaction_hash}} <- {:format, Chain.string_to_transaction_hash(transaction_hash_string)}, - {:not_found, {:ok, transaction}} <- - {:not_found, Chain.hash_to_transaction(transaction_hash, @api_true)}, - {:ok, false} <- AccessHelper.restricted_access?(to_string(transaction.from_address_hash), params), - {:ok, false} <- AccessHelper.restricted_access?(to_string(transaction.to_address_hash), params) do + with {:ok, _transaction, transaction_hash} <- validate_transaction(transaction_hash_string, params) do full_options = [ necessity_by_association: %{ @@ -312,7 +295,7 @@ defmodule BlockScoutWeb.API.V2.TransactionController do |> put_status(200) |> render(:logs, %{ tx_hash: transaction_hash, - logs: logs, + logs: logs |> maybe_preload_ens(), next_page_params: next_page_params }) end @@ -323,16 +306,12 @@ defmodule BlockScoutWeb.API.V2.TransactionController do """ @spec state_changes(Plug.Conn.t(), map()) :: Plug.Conn.t() | {atom(), any()} def state_changes(conn, %{"transaction_hash_param" => transaction_hash_string} = params) do - with {:format, {:ok, transaction_hash}} <- {:format, Chain.string_to_transaction_hash(transaction_hash_string)}, - {:not_found, {:ok, transaction}} <- - {:not_found, - Chain.hash_to_transaction(transaction_hash, - necessity_by_association: - Map.merge(@transaction_necessity_by_association, %{[block: [miner: :names]] => :optional}), - api?: true - )}, - {:ok, false} <- AccessHelper.restricted_access?(to_string(transaction.from_address_hash), params), - {:ok, false} <- AccessHelper.restricted_access?(to_string(transaction.to_address_hash), params) do + with {:ok, transaction, _transaction_hash} <- + validate_transaction(transaction_hash_string, params, + necessity_by_association: + Map.merge(@transaction_necessity_by_association, %{[block: [miner: :names]] => :optional}), + api?: true + ) do state_changes_plus_next_page = transaction |> TransactionStateHelper.state_changes(params |> paging_options() |> Keyword.merge(api?: true)) @@ -370,10 +349,52 @@ defmodule BlockScoutWeb.API.V2.TransactionController do conn |> put_status(200) |> render(:transactions_watchlist, %{ - transactions: transactions, + transactions: transactions |> maybe_preload_ens(), next_page_params: next_page_params, watchlist_names: watchlist_names }) end end + + def summary(conn, %{"transaction_hash_param" => transaction_hash_string, "just_request_body" => "true"} = params) do + with {:tx_interpreter_enabled, true} <- {:tx_interpreter_enabled, TransactionInterpretationService.enabled?()}, + {:ok, transaction, _transaction_hash} <- validate_transaction(transaction_hash_string, params) do + conn + |> json(TransactionInterpretationService.get_request_body(transaction)) + end + end + + @doc """ + Function to handle GET requests to `/api/v2/transactions/:transaction_hash_param/summary` endpoint. + """ + @spec summary(Plug.Conn.t(), map()) :: + {:format, :error} + | {:not_found, {:error, :not_found}} + | {:restricted_access, true} + | {:tx_interpreter_enabled, boolean} + | Plug.Conn.t() + def summary(conn, %{"transaction_hash_param" => transaction_hash_string} = params) do + with {:tx_interpreter_enabled, true} <- {:tx_interpreter_enabled, TransactionInterpretationService.enabled?()}, + {:ok, transaction, _transaction_hash} <- validate_transaction(transaction_hash_string, params) do + response = + case TransactionInterpretationService.interpret(transaction) do + {:ok, response} -> response + {:error, %Jason.DecodeError{}} -> %{error: "Error while tx interpreter response decoding"} + {:error, error} -> %{error: error} + end + + conn + |> json(response) + end + end + + defp validate_transaction(transaction_hash_string, params, options \\ @api_true) do + with {:format, {:ok, transaction_hash}} <- {:format, Chain.string_to_transaction_hash(transaction_hash_string)}, + {:not_found, {:ok, transaction}} <- + {:not_found, Chain.hash_to_transaction(transaction_hash, options)}, + {:ok, false} <- AccessHelper.restricted_access?(to_string(transaction.from_address_hash), params), + {:ok, false} <- AccessHelper.restricted_access?(to_string(transaction.to_address_hash), params) do + {:ok, transaction, transaction_hash} + end + end end diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/withdrawal_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/withdrawal_controller.ex index fc26823e5211..4282d16d4fbb 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/withdrawal_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/api/v2/withdrawal_controller.ex @@ -5,6 +5,7 @@ defmodule BlockScoutWeb.API.V2.WithdrawalController do only: [paging_options: 1, next_page_params: 3, split_list_by_page: 1] import BlockScoutWeb.PagingHelper, only: [delete_parameters_from_next_page_params: 1] + import Explorer.MicroserviceInterfaces.BENS, only: [maybe_preload_ens: 1] alias Explorer.Chain @@ -20,7 +21,7 @@ defmodule BlockScoutWeb.API.V2.WithdrawalController do conn |> put_status(200) - |> render(:withdrawals, %{withdrawals: withdrawals, next_page_params: next_page_params}) + |> render(:withdrawals, %{withdrawals: withdrawals |> maybe_preload_ens(), next_page_params: next_page_params}) end def withdrawals_counters(conn, _params) do diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/recent_transactions_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/recent_transactions_controller.ex index f3406fc6cab6..0863579eab95 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/recent_transactions_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/recent_transactions_controller.ex @@ -4,7 +4,7 @@ defmodule BlockScoutWeb.RecentTransactionsController do import Explorer.Chain.SmartContract, only: [burn_address_hash_string: 0] alias Explorer.{Chain, PagingOptions} - alias Explorer.Chain.Hash + alias Explorer.Chain.{DenormalizationHelper, Hash} alias Phoenix.View {:ok, burn_address_hash} = Chain.string_to_address_hash(burn_address_hash_string()) @@ -13,17 +13,22 @@ defmodule BlockScoutWeb.RecentTransactionsController do def index(conn, _params) do if ajax?(conn) do recent_transactions = - Chain.recent_collated_transactions(true, - necessity_by_association: %{ - :block => :required, - [created_contract_address: :names] => :optional, - [from_address: :names] => :optional, - [to_address: :names] => :optional, - [created_contract_address: :smart_contract] => :optional, - [from_address: :smart_contract] => :optional, - [to_address: :smart_contract] => :optional - }, - paging_options: %PagingOptions{page_size: 5} + Chain.recent_collated_transactions( + true, + DenormalizationHelper.extend_block_necessity( + [ + necessity_by_association: %{ + [created_contract_address: :names] => :optional, + [from_address: :names] => :optional, + [to_address: :names] => :optional, + [created_contract_address: :smart_contract] => :optional, + [from_address: :smart_contract] => :optional, + [to_address: :smart_contract] => :optional + }, + paging_options: %PagingOptions{page_size: 5} + ], + :required + ) ) transactions = diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/tokens/inventory_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/tokens/inventory_controller.ex index 2d09db084535..1b7f2c5bb560 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/tokens/inventory_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/tokens/inventory_controller.ex @@ -20,6 +20,7 @@ defmodule BlockScoutWeb.Tokens.InventoryController do unique_token_instances = Chain.address_to_unique_tokens( token.contract_address_hash, + token, unique_tokens_paging_options(params) ) diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/tokens/tokens_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/tokens/tokens_controller.ex index a0bae11f5ef8..17ee2823fcd5 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/tokens/tokens_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/tokens/tokens_controller.ex @@ -3,7 +3,7 @@ defmodule BlockScoutWeb.TokensController do import BlockScoutWeb.Chain, only: [paging_options: 1, next_page_params: 3, split_list_by_page: 1] alias BlockScoutWeb.{Controller, TokensView} - alias Explorer.Chain + alias Explorer.Chain.Token alias Phoenix.View def index(conn, %{"type" => "JSON"} = params) do @@ -18,7 +18,7 @@ defmodule BlockScoutWeb.TokensController do params |> paging_options() - tokens = Chain.list_top_tokens(filter, paging_params) + tokens = Token.list_top(filter, paging_params) {tokens_page, next_page} = split_list_by_page(tokens) diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/transaction_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/transaction_controller.ex index 51a75bf85fcb..a31baf1ea923 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/transaction_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/transaction_controller.ex @@ -26,6 +26,7 @@ defmodule BlockScoutWeb.TransactionController do alias Explorer.{Chain, Market} alias Explorer.Chain.Cache.Transaction, as: TransactionCache + alias Explorer.Chain.DenormalizationHelper alias Phoenix.View @necessity_by_association %{ @@ -42,7 +43,6 @@ defmodule BlockScoutWeb.TransactionController do @default_options [ necessity_by_association: %{ - :block => :required, [created_contract_address: :names] => :optional, [from_address: :names] => :optional, [to_address: :names] => :optional, @@ -55,6 +55,7 @@ defmodule BlockScoutWeb.TransactionController do def index(conn, %{"type" => "JSON"} = params) do options = @default_options + |> DenormalizationHelper.extend_block_necessity(:required) |> Keyword.merge(paging_options(params)) full_options = @@ -152,10 +153,7 @@ defmodule BlockScoutWeb.TransactionController do :ok <- Chain.check_transaction_exists(transaction_hash) do if Chain.transaction_has_token_transfers?(transaction_hash) do with {:ok, transaction} <- - Chain.hash_to_transaction( - transaction_hash, - necessity_by_association: @necessity_by_association - ), + Chain.hash_to_transaction(transaction_hash, necessity_by_association: @necessity_by_association), {:ok, false} <- AccessHelper.restricted_access?(to_string(transaction.from_address_hash), params), {:ok, false} <- AccessHelper.restricted_access?(to_string(transaction.to_address_hash), params) do render( @@ -190,10 +188,7 @@ defmodule BlockScoutWeb.TransactionController do end else with {:ok, transaction} <- - Chain.hash_to_transaction( - transaction_hash, - necessity_by_association: @necessity_by_association - ), + Chain.hash_to_transaction(transaction_hash, necessity_by_association: @necessity_by_association), {:ok, false} <- AccessHelper.restricted_access?(to_string(transaction.from_address_hash), params), {:ok, false} <- AccessHelper.restricted_access?(to_string(transaction.to_address_hash), params) do render( diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/transaction_internal_transaction_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/transaction_internal_transaction_controller.ex index 4c6a017e776d..5ea1e447211a 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/transaction_internal_transaction_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/transaction_internal_transaction_controller.ex @@ -8,6 +8,7 @@ defmodule BlockScoutWeb.TransactionInternalTransactionController do alias BlockScoutWeb.{AccessHelper, Controller, InternalTransactionView, TransactionController} alias Explorer.{Chain, Market} + alias Explorer.Chain.DenormalizationHelper alias Phoenix.View def index(conn, %{"transaction_id" => transaction_hash_string, "type" => "JSON"} = params) do @@ -17,20 +18,19 @@ defmodule BlockScoutWeb.TransactionInternalTransactionController do {:ok, false} <- AccessHelper.restricted_access?(to_string(transaction.from_address_hash), params), {:ok, false} <- AccessHelper.restricted_access?(to_string(transaction.to_address_hash), params) do full_options = - Keyword.merge( - [ - necessity_by_association: %{ - [created_contract_address: :names] => :optional, - [from_address: :names] => :optional, - [to_address: :names] => :optional, - [transaction: :block] => :optional, - [created_contract_address: :smart_contract] => :optional, - [from_address: :smart_contract] => :optional, - [to_address: :smart_contract] => :optional - } - ], - paging_options(params) - ) + [ + necessity_by_association: %{ + [created_contract_address: :names] => :optional, + [from_address: :names] => :optional, + [to_address: :names] => :optional, + [created_contract_address: :smart_contract] => :optional, + [from_address: :smart_contract] => :optional, + [to_address: :smart_contract] => :optional, + :transaction => :optional + } + ] + |> DenormalizationHelper.extend_transaction_block_necessity(:optional) + |> Keyword.merge(paging_options(params)) internal_transactions_plus_one = Chain.transaction_to_internal_transactions(transaction_hash, full_options) diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/verified_contracts_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/verified_contracts_controller.ex index 2059498cce2a..1eec03d72678 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/verified_contracts_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/verified_contracts_controller.ex @@ -8,6 +8,7 @@ defmodule BlockScoutWeb.VerifiedContractsController do alias BlockScoutWeb.{Controller, VerifiedContractsView} alias Explorer.Chain + alias Explorer.Chain.SmartContract alias Phoenix.View @necessity_by_association %{[address: :token] => :optional} @@ -19,7 +20,7 @@ defmodule BlockScoutWeb.VerifiedContractsController do |> Keyword.merge(current_filter(params)) |> Keyword.merge(search_query(params)) - verified_contracts_plus_one = Chain.verified_contracts(full_options) + verified_contracts_plus_one = SmartContract.verified_contracts(full_options) {verified_contracts, next_page} = split_list_by_page(verified_contracts_plus_one) items = diff --git a/apps/block_scout_web/lib/block_scout_web/microservice_interfaces/transaction_interpretation.ex b/apps/block_scout_web/lib/block_scout_web/microservice_interfaces/transaction_interpretation.ex new file mode 100644 index 000000000000..4b6d7edc7477 --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/microservice_interfaces/transaction_interpretation.ex @@ -0,0 +1,207 @@ +defmodule BlockScoutWeb.MicroserviceInterfaces.TransactionInterpretation do + @moduledoc """ + Module to interact with Transaction Interpretation Service + """ + + alias BlockScoutWeb.API.V2.{Helper, TokenView, TransactionView} + alias Explorer.Chain + alias Explorer.Chain.Transaction + alias HTTPoison.Response + + import Explorer.Utility.Microservice, only: [base_url: 2] + + require Logger + + @post_timeout :timer.minutes(5) + @request_error_msg "Error while sending request to Transaction Interpretation Service" + @api_true api?: true + @items_limit 50 + + @spec interpret(Transaction.t()) :: {:error, :disabled | binary | Jason.DecodeError.t()} | {:ok, any} + def interpret(transaction) do + if enabled?() do + url = interpret_url() + + body = prepare_request_body(transaction) + + http_post_request(url, body) + else + {:error, :disabled} + end + end + + def get_request_body(transaction) do + prepare_request_body(transaction) + end + + defp http_post_request(url, body) do + headers = [{"Content-Type", "application/json"}] + + case HTTPoison.post(url, Jason.encode!(body), headers, recv_timeout: @post_timeout) do + {:ok, %Response{body: body, status_code: 200}} -> + body |> Jason.decode() |> preload_template_variables() + + error -> + old_truncate = Application.get_env(:logger, :truncate) + Logger.configure(truncate: :infinity) + + Logger.error(fn -> + [ + "Error while sending request to microservice url: #{url}, body: #{inspect(body, limit: :infinity, printable_limit: :infinity)}: ", + inspect(error, limit: :infinity, printable_limit: :infinity) + ] + end) + + Logger.configure(truncate: old_truncate) + {:error, @request_error_msg} + end + end + + defp config do + Application.get_env(:block_scout_web, __MODULE__) + end + + def enabled?, do: config()[:enabled] + + defp interpret_url do + base_url(:block_scout_web, __MODULE__) <> "/transactions/summary" + end + + defp prepare_request_body(transaction) do + transaction = + Chain.select_repo(@api_true).preload(transaction, [ + :transaction_actions, + to_address: [:names, :smart_contract], + from_address: [:names, :smart_contract], + created_contract_address: [:names, :token, :smart_contract] + ]) + + skip_sig_provider? = false + {decoded_input, _abi_acc, _methods_acc} = Transaction.decoded_input_data(transaction, skip_sig_provider?, @api_true) + + decoded_input_data = decoded_input |> TransactionView.format_decoded_input() |> TransactionView.decoded_input() + + %{ + data: %{ + to: Helper.address_with_info(nil, transaction.to_address, transaction.to_address_hash, true), + from: + Helper.address_with_info( + nil, + transaction.from_address, + transaction.from_address_hash, + true + ), + hash: transaction.hash, + type: transaction.type, + value: transaction.value, + method: TransactionView.method_name(transaction, decoded_input), + status: transaction.status, + actions: TransactionView.transaction_actions(transaction.transaction_actions), + tx_types: TransactionView.tx_types(transaction), + raw_input: transaction.input, + decoded_input: decoded_input_data, + token_transfers: prepare_token_transfers(transaction, decoded_input) + }, + logs_data: %{items: prepare_logs(transaction)} + } + end + + defp prepare_token_transfers(transaction, decoded_input) do + full_options = + [ + necessity_by_association: %{ + [from_address: :smart_contract] => :optional, + [to_address: :smart_contract] => :optional, + [from_address: :names] => :optional, + [to_address: :names] => :optional + } + ] + |> Keyword.merge(@api_true) + + transaction.hash + |> Chain.transaction_to_token_transfers(full_options) + |> Chain.flat_1155_batch_token_transfers() + |> Enum.take(@items_limit) + |> Enum.map(&TransactionView.prepare_token_transfer(&1, nil, decoded_input)) + end + + defp prepare_logs(transaction) do + full_options = + [ + necessity_by_association: %{ + [address: :names] => :optional, + [address: :smart_contract] => :optional, + address: :optional + } + ] + |> Keyword.merge(@api_true) + + logs = + transaction.hash + |> Chain.transaction_to_logs(full_options) + |> Enum.take(@items_limit) + + decoded_logs = TransactionView.decode_logs(logs, true) + + logs + |> Enum.zip(decoded_logs) + |> Enum.map(fn {log, decoded_log} -> TransactionView.prepare_log(log, transaction.hash, decoded_log, true) end) + end + + defp preload_template_variables({:ok, %{"success" => true, "data" => %{"summaries" => summaries} = data}}) do + summaries_updated = + Enum.map(summaries, fn %{"summary_template_variables" => summary_template_variables} = summary -> + summary_template_variables_preloaded = + Enum.reduce(summary_template_variables, %{}, fn {key, value}, acc -> + Map.put(acc, key, preload_template_variable(value)) + end) + + Map.put(summary, "summary_template_variables", summary_template_variables_preloaded) + end) + + {:ok, %{"success" => true, "data" => Map.put(data, "summaries", summaries_updated)}} + end + + defp preload_template_variables(error), do: error + + defp preload_template_variable(%{"type" => "token", "value" => %{"address" => address_hash_string} = value}), + do: %{ + "type" => "token", + "value" => address_hash_string |> Chain.token_from_address_hash(@api_true) |> token_from_db() |> Map.merge(value) + } + + defp preload_template_variable(%{"type" => "address", "value" => %{"hash" => address_hash_string} = value}), + do: %{ + "type" => "address", + "value" => + address_hash_string + |> Chain.hash_to_address( + [ + necessity_by_association: %{ + :names => :optional, + :smart_contract => :optional + }, + api?: true + ], + false + ) + |> address_from_db() + |> Map.merge(value) + } + + defp preload_template_variable(other), do: other + + defp token_from_db({:error, _}), do: %{} + defp token_from_db({:ok, token}), do: TokenView.render("token.json", %{token: token}) + + defp address_from_db({:error, _}), do: %{} + + defp address_from_db({:ok, address}), + do: + Helper.address_with_info( + nil, + address, + address.hash, + true + ) +end diff --git a/apps/block_scout_web/lib/block_scout_web/models/transaction_state_helper.ex b/apps/block_scout_web/lib/block_scout_web/models/transaction_state_helper.ex index 3971a5463a64..41b5bd1cfe7b 100644 --- a/apps/block_scout_web/lib/block_scout_web/models/transaction_state_helper.ex +++ b/apps/block_scout_web/lib/block_scout_web/models/transaction_state_helper.ex @@ -112,7 +112,7 @@ defmodule BlockScoutWeb.Models.TransactionStateHelper do token_ids = if token.type == "ERC-1155" do - token_transfer.token_ids || [token_transfer.token_id] + token_transfer.token_ids else [nil] end diff --git a/apps/block_scout_web/lib/block_scout_web/notifier.ex b/apps/block_scout_web/lib/block_scout_web/notifier.ex index e340c4351df3..3a70cd316594 100644 --- a/apps/block_scout_web/lib/block_scout_web/notifier.ex +++ b/apps/block_scout_web/lib/block_scout_web/notifier.ex @@ -20,7 +20,7 @@ defmodule BlockScoutWeb.Notifier do alias Explorer.{Chain, Market, Repo} alias Explorer.Chain.Address.Counters - alias Explorer.Chain.{Address, InternalTransaction, Transaction} + alias Explorer.Chain.{Address, DenormalizationHelper, InternalTransaction, Transaction} alias Explorer.Chain.Supply.RSK alias Explorer.Chain.Transaction.History.TransactionStats alias Explorer.Counters.{AverageBlockTime, Helper} @@ -171,7 +171,9 @@ defmodule BlockScoutWeb.Notifier do all_token_transfers |> Enum.map( &(&1 - |> Repo.preload([:from_address, :to_address, :token, transaction: :block])) + |> Repo.preload( + DenormalizationHelper.extend_transaction_preload([:from_address, :to_address, :token, :transaction]) + )) ) transfers_by_token = Enum.group_by(all_token_transfers_full, fn tt -> to_string(tt.token_contract_address_hash) end) @@ -191,13 +193,11 @@ defmodule BlockScoutWeb.Notifier do end def handle_event({:chain_event, :transactions, :realtime, transactions}) do - preloads = [:block, created_contract_address: :names, from_address: :names, to_address: :names] + base_preloads = [:block, created_contract_address: :names, from_address: :names, to_address: :names] + preloads = if API_V2.enabled?(), do: [:token_transfers | base_preloads], else: base_preloads transactions - |> Enum.map( - &(&1 - |> Repo.preload(if API_V2.enabled?(), do: [:token_transfers | preloads], else: preloads)) - ) + |> Repo.preload(preloads) |> broadcast_transactions_websocket_v2() |> Enum.map(fn tx -> # Disable parsing of token transfers from websocket for transaction tab because we display token transfers at a separate tab diff --git a/apps/block_scout_web/lib/block_scout_web/paging_helper.ex b/apps/block_scout_web/lib/block_scout_web/paging_helper.ex index c9a3472c850f..406f47b6c761 100644 --- a/apps/block_scout_web/lib/block_scout_web/paging_helper.ex +++ b/apps/block_scout_web/lib/block_scout_web/paging_helper.ex @@ -3,7 +3,8 @@ defmodule BlockScoutWeb.PagingHelper do Helper for fetching filters and other url query parameters """ import Explorer.Chain, only: [string_to_transaction_hash: 1] - alias Explorer.PagingOptions + alias Explorer.Chain.Transaction + alias Explorer.{PagingOptions, SortingHelper} @page_size 50 @default_paging_options %PagingOptions{page_size: @page_size + 1} @@ -186,6 +187,7 @@ defmodule BlockScoutWeb.PagingHelper do def search_query(_), do: [] + @spec tokens_sorting(%{required(String.t()) => String.t()}) :: [{:sorting, SortingHelper.sorting_params()}] def tokens_sorting(%{"sort" => sort_field, "order" => order}) do [sorting: do_tokens_sorting(sort_field, order)] end @@ -199,4 +201,35 @@ defmodule BlockScoutWeb.PagingHelper do defp do_tokens_sorting("circulating_market_cap", "asc"), do: [asc_nulls_first: :circulating_market_cap] defp do_tokens_sorting("circulating_market_cap", "desc"), do: [desc_nulls_last: :circulating_market_cap] defp do_tokens_sorting(_, _), do: [] + + @spec smart_contracts_sorting(%{required(String.t()) => String.t()}) :: [{:sorting, SortingHelper.sorting_params()}] + def smart_contracts_sorting(%{"sort" => sort_field, "order" => order}) do + [sorting: do_smart_contracts_sorting(sort_field, order)] + end + + def smart_contracts_sorting(_), do: [] + + defp do_smart_contracts_sorting("balance", "asc"), do: [{:asc_nulls_first, :fetched_coin_balance, :address}] + defp do_smart_contracts_sorting("balance", "desc"), do: [{:desc_nulls_last, :fetched_coin_balance, :address}] + defp do_smart_contracts_sorting("txs_count", "asc"), do: [{:asc_nulls_first, :transactions_count, :address}] + defp do_smart_contracts_sorting("txs_count", "desc"), do: [{:desc_nulls_last, :transactions_count, :address}] + defp do_smart_contracts_sorting(_, _), do: [] + + @spec address_transactions_sorting(%{required(String.t()) => String.t()}) :: [ + {:sorting, SortingHelper.sorting_params()} + ] + def address_transactions_sorting(%{"sort" => sort_field, "order" => order}) do + [sorting: do_address_transaction_sorting(sort_field, order)] + end + + def address_transactions_sorting(_), do: [] + + defp do_address_transaction_sorting("value", "asc"), do: [asc: :value] + defp do_address_transaction_sorting("value", "desc"), do: [desc: :value] + defp do_address_transaction_sorting("fee", "asc"), do: [{:dynamic, :fee, :asc_nulls_first, Transaction.dynamic_fee()}] + + defp do_address_transaction_sorting("fee", "desc"), + do: [{:dynamic, :fee, :desc_nulls_last, Transaction.dynamic_fee()}] + + defp do_address_transaction_sorting(_, _), do: [] end diff --git a/apps/block_scout_web/lib/block_scout_web/realtime_event_handler.ex b/apps/block_scout_web/lib/block_scout_web/realtime_event_handler.ex index 7d029f17f885..1fedc2dc3dba 100644 --- a/apps/block_scout_web/lib/block_scout_web/realtime_event_handler.ex +++ b/apps/block_scout_web/lib/block_scout_web/realtime_event_handler.ex @@ -24,11 +24,8 @@ defmodule BlockScoutWeb.RealtimeEventHandler do Subscriber.to(:address_coin_balances, :on_demand) Subscriber.to(:address_current_token_balances, :on_demand) Subscriber.to(:address_token_balances, :on_demand) - Subscriber.to(:contract_verification_result, :on_demand) Subscriber.to(:token_total_supply, :on_demand) Subscriber.to(:changed_bytecode, :on_demand) - Subscriber.to(:smart_contract_was_verified, :on_demand) - Subscriber.to(:smart_contract_was_not_verified, :on_demand) Subscriber.to(:eth_bytecode_db_lookup_started, :on_demand) Subscriber.to(:zkevm_confirmed_batches, :realtime) # Does not come from the indexer diff --git a/apps/block_scout_web/lib/block_scout_web/schema/types.ex b/apps/block_scout_web/lib/block_scout_web/schema/types.ex index 99f47a29163b..d81f6eca5137 100644 --- a/apps/block_scout_web/lib/block_scout_web/schema/types.ex +++ b/apps/block_scout_web/lib/block_scout_web/schema/types.ex @@ -130,7 +130,6 @@ defmodule BlockScoutWeb.Schema.Types do field(:amounts, list_of(:decimal)) field(:block_number, :integer) field(:log_index, :integer) - field(:token_id, :decimal) field(:token_ids, list_of(:decimal)) field(:from_address_hash, :address_hash) field(:to_address_hash, :address_hash) diff --git a/apps/block_scout_web/lib/block_scout_web/smart_contract_realtime_event_handler.ex b/apps/block_scout_web/lib/block_scout_web/smart_contract_realtime_event_handler.ex new file mode 100644 index 000000000000..e94e8a3bb9bb --- /dev/null +++ b/apps/block_scout_web/lib/block_scout_web/smart_contract_realtime_event_handler.ex @@ -0,0 +1,28 @@ +defmodule BlockScoutWeb.SmartContractRealtimeEventHandler do + @moduledoc """ + Subscribing process for smart contract verification related broadcast events from realtime. + """ + + use GenServer + + alias BlockScoutWeb.Notifier + alias Explorer.Chain.Events.Subscriber + + def start_link(_) do + GenServer.start_link(__MODULE__, [], name: __MODULE__) + end + + @impl true + def init([]) do + Subscriber.to(:contract_verification_result, :on_demand) + Subscriber.to(:smart_contract_was_verified, :on_demand) + Subscriber.to(:smart_contract_was_not_verified, :on_demand) + {:ok, []} + end + + @impl true + def handle_info(event, state) do + Notifier.handle_event(event) + {:noreply, state} + end +end diff --git a/apps/block_scout_web/lib/block_scout_web/smart_contracts_api_v2_router.ex b/apps/block_scout_web/lib/block_scout_web/smart_contracts_api_v2_router.ex index d3f1d5e162f7..ad8bf8ad9609 100644 --- a/apps/block_scout_web/lib/block_scout_web/smart_contracts_api_v2_router.ex +++ b/apps/block_scout_web/lib/block_scout_web/smart_contracts_api_v2_router.ex @@ -27,6 +27,7 @@ defmodule BlockScoutWeb.SmartContractsApiV2Router do get("/:address_hash/methods-read-proxy", V2.SmartContractController, :methods_read_proxy) get("/:address_hash/methods-write-proxy", V2.SmartContractController, :methods_write_proxy) post("/:address_hash/query-read-method", V2.SmartContractController, :query_read_method) + get("/:address_hash/solidityscan-report", V2.SmartContractController, :solidityscan_report) get("/verification/config", V2.VerificationController, :config) diff --git a/apps/block_scout_web/lib/block_scout_web/templates/address_logs/_logs.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/address_logs/_logs.html.eex index eaa5765bc29b..9e213f9457f0 100644 --- a/apps/block_scout_web/lib/block_scout_web/templates/address_logs/_logs.html.eex +++ b/apps/block_scout_web/lib/block_scout_web/templates/address_logs/_logs.html.eex @@ -27,46 +27,43 @@ ) %> - <%= case decoded_result do %> - <% {:error, :could_not_decode} -> %> -
<%= gettext "Decoded" %>
-
-
- <%= gettext "Failed to decode log data." %> -
- <% {:ok, method_id, text, mapping} -> %> -
<%= gettext "Decoded" %>
-
- - - - - - - - - -
Method Id0x<%= method_id %>
Call<%= text %>
- <%= render BlockScoutWeb.LogView, "_data_decoded_view.html", mapping: mapping %> - <% {:error, :contract_not_verified, results} -> %> - <%= for {:ok, method_id, text, mapping} <- results do %> -
<%= gettext "Decoded" %>
-
- - - - - - - - - -
Method Id0x<%= method_id %>
Call<%= text %>
- <%= render BlockScoutWeb.LogView, "_data_decoded_view.html", mapping: mapping %> - + <%= case decoded_result do %> + <% {:error, :could_not_decode} -> %> +
<%= gettext "Decoded" %>
+
+
+ <%= gettext "Failed to decode log data." %> +
+ <% {:ok, method_id, text, mapping} -> %> +
<%= gettext "Decoded" %>
+
+ + + + + + + + + +
Method Id0x<%= method_id %>
Call<%= text %>
+ <%= render BlockScoutWeb.LogView, "_data_decoded_view.html", mapping: mapping %> + <% {:error, :contract_not_verified, results} -> %> + <%= for {:ok, method_id, text, mapping} <- results do %> +
<%= gettext "Decoded" %>
+
+ + + + + + + + + +
Method Id0x<%= method_id %>
Call<%= text %>
+ <%= render BlockScoutWeb.LogView, "_data_decoded_view.html", mapping: mapping %> <% end %> - <% _ -> %> - <%= nil %> <% end %>
<%= gettext "Topics" %>
diff --git a/apps/block_scout_web/lib/block_scout_web/templates/block/_link.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/block/_link.html.eex index d910c2ad1941..c498a395bebf 100644 --- a/apps/block_scout_web/lib/block_scout_web/templates/block/_link.html.eex +++ b/apps/block_scout_web/lib/block_scout_web/templates/block/_link.html.eex @@ -1,4 +1,4 @@ <%= link( gettext("Block #%{number}", number: to_string(@block.number)), - to: block_path(BlockScoutWeb.Endpoint, :show, @block) + to: block_path(BlockScoutWeb.Endpoint, :show, @block.hash) ) %> diff --git a/apps/block_scout_web/lib/block_scout_web/templates/block/_tile.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/block/_tile.html.eex index d7b9c2613400..452c73a3feb0 100644 --- a/apps/block_scout_web/lib/block_scout_web/templates/block/_tile.html.eex +++ b/apps/block_scout_web/lib/block_scout_web/templates/block/_tile.html.eex @@ -1,4 +1,4 @@ -<% burned_fee = if !is_nil(@block.base_fee_per_gas), do: Wei.mult(@block.base_fee_per_gas, BlockBurnedFeeCounter.fetch(@block.hash)), else: nil %> +<% burnt_fees = if !is_nil(@block.base_fee_per_gas), do: Wei.mult(@block.base_fee_per_gas, BlockBurntFeeCounter.fetch(@block.hash)), else: nil %> <% priority_fee = if !is_nil(@block.base_fee_per_gas), do: BlockPriorityFeeCounter.fetch(@block.hash), else: nil %>
@@ -61,7 +61,7 @@ <%= format_wei_value(%Wei{value: priority_fee}, :ether) %> <%= gettext "Priority Fees" %> - <%= format_wei_value(burned_fee, :ether) %> <%= gettext "Burnt Fees" %> + <%= format_wei_value(burnt_fees, :ether) %> <%= gettext "Burnt Fees" %> <% end %> <%= formatted_gas(@block.gas_limit) %> <%= gettext "Gas Limit" %> diff --git a/apps/block_scout_web/lib/block_scout_web/templates/block/overview.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/block/overview.html.eex index 332ae2b1392a..feee728643a1 100644 --- a/apps/block_scout_web/lib/block_scout_web/templates/block/overview.html.eex +++ b/apps/block_scout_web/lib/block_scout_web/templates/block/overview.html.eex @@ -1,4 +1,4 @@ -<% burned_fee = if !is_nil(@block.base_fee_per_gas), do: Wei.mult(@block.base_fee_per_gas, BlockBurnedFeeCounter.fetch(@block.hash)), else: nil %> +<% burnt_fees = if !is_nil(@block.base_fee_per_gas), do: Wei.mult(@block.base_fee_per_gas, BlockBurntFeeCounter.fetch(@block.hash)), else: nil %> <% priority_fee = if !is_nil(@block.base_fee_per_gas), do: BlockPriorityFeeCounter.fetch(@block.hash), else: nil %>
<%= render BlockScoutWeb.Advertisement.TextAdView, "index.html", conn: @conn %> @@ -212,10 +212,10 @@
<%= render BlockScoutWeb.CommonComponentsView, "_i_tooltip_2.html", - text: Explorer.coin_name() <> " " <> gettext("burned from transactions included in the block (Base fee (per unit of gas) * Gas Used).") %> + text: Explorer.coin_name() <> " " <> gettext("burnt from transactions included in the block (Base fee (per unit of gas) * Gas Used).") %> <%= gettext("Burnt Fees") %>
-
<%= format_wei_value(burned_fee, :ether) %>
+
<%= format_wei_value(burnt_fees, :ether) %>
@@ -226,7 +226,7 @@
<%= format_wei_value(%Wei{value: priority_fee}, :ether) %>
- <% end %> + <% end %> <%= if show_reward?(@block.rewards) do %>
<%= for block_reward <- @block.rewards do %> @@ -268,4 +268,4 @@
-<%= render BlockScoutWeb.Advertisement.BannersAdView, "_banner_728.html", conn: @conn %> +<%= render BlockScoutWeb.Advertisement.BannersAdView, "_banner_728.html", conn: @conn %> diff --git a/apps/block_scout_web/lib/block_scout_web/templates/chain/gas_price_oracle_legend_item.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/chain/gas_price_oracle_legend_item.html.eex index 4b84dce35873..f07899ea9bf8 100644 --- a/apps/block_scout_web/lib/block_scout_web/templates/chain/gas_price_oracle_legend_item.html.eex +++ b/apps/block_scout_web/lib/block_scout_web/templates/chain/gas_price_oracle_legend_item.html.eex @@ -8,7 +8,7 @@
-
<%= "#{gas_prices_from_oracle["average"]}" <> " " %><%= gettext "Gwei" %>
+
<%= "#{gas_prices_from_oracle[:average]}" <> " " %><%= gettext "Gwei" %>
-
<%= gettext "Slow" %><%= gas_prices_from_oracle["slow"] %> <%= gettext "Gwei" %>
-
<%= gettext "Average" %><%= gas_prices_from_oracle["average"] %> <%= gettext "Gwei" %>
-
<%= gettext "Fast" %><%= gas_prices_from_oracle["fast"] %> <%= gettext "Gwei" %>
+
<%= gettext "Slow" %><%= gas_prices_from_oracle[:slow] %> <%= gettext "Gwei" %>
+
<%= gettext "Average" %><%= gas_prices_from_oracle[:average] %> <%= gettext "Gwei" %>
+
<%= gettext "Fast" %><%= gas_prices_from_oracle[:fast] %> <%= gettext "Gwei" %>
" > @@ -40,4 +40,4 @@ <% end %> <% end %>
-
\ No newline at end of file + diff --git a/apps/block_scout_web/lib/block_scout_web/templates/layout/app.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/layout/app.html.eex index 3690228888da..6686ce415c5a 100644 --- a/apps/block_scout_web/lib/block_scout_web/templates/layout/app.html.eex +++ b/apps/block_scout_web/lib/block_scout_web/templates/layout/app.html.eex @@ -69,6 +69,9 @@ +
+ <%= raw("The new Blockscout UI is now open source! Learn how to deploy it here") %> +
<% show_maintenance_alert = Application.get_env(:block_scout_web, BlockScoutWeb.Chain)[:show_maintenance_alert] %> <%= if show_maintenance_alert do %>
diff --git a/apps/block_scout_web/lib/block_scout_web/templates/robots/sitemap.xml.eex b/apps/block_scout_web/lib/block_scout_web/templates/robots/sitemap.xml.eex index 9f3cbc669802..55d8b1a6115a 100644 --- a/apps/block_scout_web/lib/block_scout_web/templates/robots/sitemap.xml.eex +++ b/apps/block_scout_web/lib/block_scout_web/templates/robots/sitemap.xml.eex @@ -35,7 +35,7 @@ <% end %> - <% tokens = Chain.list_top_tokens(nil, params) %> + <% tokens = Token.list_top(nil, params) %> <%= for token <- tokens do %> <%= host %>/token/<%= to_string(token.contract_address_hash) %> diff --git a/apps/block_scout_web/lib/block_scout_web/templates/tokens/transfer/_token_transfer.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/tokens/transfer/_token_transfer.html.eex index d1d151ffe2b5..73b6bbf5afe5 100644 --- a/apps/block_scout_web/lib/block_scout_web/templates/tokens/transfer/_token_transfer.html.eex +++ b/apps/block_scout_web/lib/block_scout_web/templates/tokens/transfer/_token_transfer.html.eex @@ -44,7 +44,7 @@ to: block_path(BlockScoutWeb.Endpoint, :show, @token_transfer.block_number) ) %> - +
diff --git a/apps/block_scout_web/lib/block_scout_web/templates/transaction/overview.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/transaction/overview.html.eex index c4b9ab55abcc..174da135d13a 100644 --- a/apps/block_scout_web/lib/block_scout_web/templates/transaction/overview.html.eex +++ b/apps/block_scout_web/lib/block_scout_web/templates/transaction/overview.html.eex @@ -7,7 +7,7 @@ <% base_fee_per_gas = if block, do: block.base_fee_per_gas, else: nil %> <% max_priority_fee_per_gas = @transaction.max_priority_fee_per_gas %> <% max_fee_per_gas = @transaction.max_fee_per_gas %> -<% burned_fee = +<% burnt_fees = if !is_nil(max_fee_per_gas) and !is_nil(@transaction.gas_used) and !is_nil(base_fee_per_gas) do if Decimal.compare(max_fee_per_gas.value, 0) == :eq do %Wei{value: Decimal.new(0)} @@ -17,7 +17,7 @@ else nil end %> -<% %Wei{value: burned_fee_decimal} = if is_nil(burned_fee), do: %Wei{value: Decimal.new(0)}, else: burned_fee %> +<% %Wei{value: burnt_fee_decimal} = if is_nil(burnt_fees), do: %Wei{value: Decimal.new(0)}, else: burnt_fees %> <% priority_fee_per_gas = if is_nil(max_priority_fee_per_gas) or is_nil(base_fee_per_gas), do: nil, else: Enum.min_by([max_priority_fee_per_gas, Wei.sub(max_fee_per_gas, base_fee_per_gas)], fn x -> Wei.to(x, :wei) end) %> <% priority_fee = if is_nil(priority_fee_per_gas), do: nil, else: Wei.mult(priority_fee_per_gas, @transaction.gas_used) %> <% decoded_input_data = decoded_input_data(@transaction) %> @@ -470,16 +470,16 @@
<%= format_wei_value(priority_fee, :ether) %>
<% end %> - <%= if !is_nil(burned_fee) do %> + <%= if !is_nil(burnt_fees) do %>
<%= render BlockScoutWeb.CommonComponentsView, "_i_tooltip_2.html", - text: gettext("Amount of") <> " " <> Explorer.coin_name() <> " " <> gettext("burned for this transaction. Equals Block Base Fee per Gas * Gas Used.") %> + text: gettext("Amount of") <> " " <> Explorer.coin_name() <> " " <> gettext("burnt for this transaction. Equals Block Base Fee per Gas * Gas Used.") %> <%= gettext "Transaction Burnt Fee" %>
-
<%= format_wei_value(burned_fee, :ether) %> +
<%= format_wei_value(burnt_fees, :ether) %> <%= unless empty_exchange_rate?(@exchange_rate) do %> - ( data-usd-exchange-rate=<%= @exchange_rate.usd_value %>>) + ( data-usd-exchange-rate=<%= @exchange_rate.usd_value %>>) <% end %>
diff --git a/apps/block_scout_web/lib/block_scout_web/views/address_contract_view.ex b/apps/block_scout_web/lib/block_scout_web/views/address_contract_view.ex index 230f17c8ed60..01b13e6a71d3 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/address_contract_view.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/address_contract_view.ex @@ -1,6 +1,8 @@ defmodule BlockScoutWeb.AddressContractView do use BlockScoutWeb, :view + require Logger + import Explorer.Helper, only: [decode_data: 2] alias ABI.FunctionSelector @@ -132,6 +134,12 @@ defmodule BlockScoutWeb.AddressContractView do chain_id = Application.get_env(:explorer, Explorer.ThirdPartyIntegrations.Sourcify)[:chain_id] repo_url = Application.get_env(:explorer, Explorer.ThirdPartyIntegrations.Sourcify)[:repo_url] match = if partial_match, do: "/partial_match/", else: "/full_match/" - repo_url <> match <> chain_id <> "/" <> checksummed_hash <> "/" + + if chain_id do + repo_url <> match <> chain_id <> "/" <> checksummed_hash <> "/" + else + Logger.warning("chain_id is nil. Please set CHAIN_ID env variable.") + nil + end end end diff --git a/apps/block_scout_web/lib/block_scout_web/views/address_view.ex b/apps/block_scout_web/lib/block_scout_web/views/address_view.ex index af46f31ca25c..ed3f54040657 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/address_view.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/address_view.ex @@ -497,6 +497,14 @@ defmodule BlockScoutWeb.AddressView do def contract_interaction_disabled?, do: Application.get_env(:block_scout_web, :contract)[:disable_interaction] + @doc """ + Decodes given log + """ + @spec decode(Log.t(), Transaction.t()) :: + {:ok, String.t(), String.t(), map()} + | {:error, atom()} + | {:error, atom(), list()} + | {{:error, :contract_not_verified, list()}, any()} def decode(log, transaction) do {result, _contracts_acc, _events_acc} = Log.decode(log, transaction, [], true) result diff --git a/apps/block_scout_web/lib/block_scout_web/views/api/rpc/address_view.ex b/apps/block_scout_web/lib/block_scout_web/views/api/rpc/address_view.ex index 2d9deed6efb4..263462e9cc54 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/api/rpc/address_view.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/api/rpc/address_view.ex @@ -113,7 +113,7 @@ defmodule BlockScoutWeb.API.RPC.AddressView do "to" => "#{transaction.to_address_hash}", "value" => "#{transaction.value.value}", "gas" => "#{transaction.gas}", - "gasPrice" => "#{transaction.gas_price.value}", + "gasPrice" => "#{transaction.gas_price && transaction.gas_price.value}", "isError" => if(transaction.status == :ok, do: "0", else: "1"), "txreceipt_status" => if(transaction.status == :ok, do: "1", else: "0"), "input" => "#{transaction.input}", @@ -160,7 +160,7 @@ defmodule BlockScoutWeb.API.RPC.AddressView do "tokenDecimal" => to_string(token_transfer.token_decimals), "transactionIndex" => to_string(token_transfer.transaction_index), "gas" => to_string(token_transfer.transaction_gas), - "gasPrice" => to_string(token_transfer.transaction_gas_price.value), + "gasPrice" => to_string(token_transfer.transaction_gas_price && token_transfer.transaction_gas_price.value), "gasUsed" => to_string(token_transfer.transaction_gas_used), "cumulativeGasUsed" => to_string(token_transfer.transaction_cumulative_gas_used), "input" => to_string(token_transfer.transaction_input), diff --git a/apps/block_scout_web/lib/block_scout_web/views/api/rpc/logs_view.ex b/apps/block_scout_web/lib/block_scout_web/views/api/rpc/logs_view.ex index 6f2933d7c63b..f52ab8fadccd 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/api/rpc/logs_view.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/api/rpc/logs_view.ex @@ -44,6 +44,8 @@ defmodule BlockScoutWeb.API.RPC.LogsView do |> integer_to_hex() end + defp datetime_to_hex(nil), do: nil + defp datetime_to_hex(datetime) do datetime |> DateTime.to_unix() diff --git a/apps/block_scout_web/lib/block_scout_web/views/api/rpc/transaction_view.ex b/apps/block_scout_web/lib/block_scout_web/views/api/rpc/transaction_view.ex index 4b81b110914f..4a18643aa354 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/api/rpc/transaction_view.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/api/rpc/transaction_view.ex @@ -2,6 +2,7 @@ defmodule BlockScoutWeb.API.RPC.TransactionView do use BlockScoutWeb, :view alias BlockScoutWeb.API.RPC.RPCView + alias Explorer.Chain.Transaction def render("gettxinfo.json", %{ transaction: transaction, @@ -58,7 +59,7 @@ defmodule BlockScoutWeb.API.RPC.TransactionView do defp prepare_transaction(transaction, block_height, logs, next_page_params) do %{ "hash" => "#{transaction.hash}", - "timeStamp" => "#{DateTime.to_unix(transaction.block.timestamp)}", + "timeStamp" => "#{DateTime.to_unix(Transaction.block_timestamp(transaction))}", "blockNumber" => "#{transaction.block_number}", "confirmations" => "#{block_height - transaction.block_number}", "success" => if(transaction.status == :ok, do: true, else: false), diff --git a/apps/block_scout_web/lib/block_scout_web/views/api/v2/address_view.ex b/apps/block_scout_web/lib/block_scout_web/views/api/v2/address_view.ex index fec26f5d2325..1b29a23d4b73 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/api/v2/address_view.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/api/v2/address_view.ex @@ -56,7 +56,7 @@ defmodule BlockScoutWeb.API.V2.AddressView do end def render("nft_list.json", %{token_instances: token_instances, token: token, next_page_params: next_page_params}) do - %{"items" => Enum.map(token_instances, &prepare_nft(&1, token, true)), "next_page_params" => next_page_params} + %{"items" => Enum.map(token_instances, &prepare_nft(&1, token)), "next_page_params" => next_page_params} end def render("nft_list.json", %{token_instances: token_instances, next_page_params: next_page_params}) do @@ -80,11 +80,20 @@ defmodule BlockScoutWeb.API.V2.AddressView do def prepare_address(address, conn \\ nil) do base_info = Helper.address_with_info(conn, address, address.hash, true) - is_proxy = AddressView.smart_contract_is_proxy?(address, @api_true) + + {:ok, address_with_smart_contract} = + Chain.hash_to_address( + address.hash, + [necessity_by_association: %{:smart_contract => :optional}], + false + ) + + is_proxy = AddressView.smart_contract_is_proxy?(address_with_smart_contract, @api_true) {implementation_address, implementation_name} = with true <- is_proxy, - {address, name} <- SmartContract.get_implementation_address_hash(address.smart_contract, @api_true), + {address, name} <- + SmartContract.get_implementation_address_hash(address_with_smart_contract.smart_contract, @api_true), false <- is_nil(address), {:ok, address_hash} <- Chain.string_to_address_hash(address), checksummed_address <- Address.checksum(address_hash) do @@ -118,7 +127,8 @@ defmodule BlockScoutWeb.API.V2.AddressView do "has_methods_read" => AddressView.smart_contract_with_read_only_functions?(address), "has_methods_write" => AddressView.smart_contract_with_write_functions?(address), "has_methods_read_proxy" => is_proxy, - "has_methods_write_proxy" => AddressView.smart_contract_with_write_functions?(address) && is_proxy, + "has_methods_write_proxy" => + AddressView.smart_contract_with_write_functions?(address_with_smart_contract) && is_proxy, "has_decompiled_code" => AddressView.has_decompiled_code?(address), "has_validated_blocks" => Counters.check_if_validated_blocks_at_address(address.hash, @api_true), "has_logs" => Counters.check_if_logs_at_address(address.hash, @api_true), @@ -175,13 +185,13 @@ defmodule BlockScoutWeb.API.V2.AddressView do end defp prepare_nft(nft) do - prepare_nft(nft, nft.token, false) + prepare_nft(nft, nft.token) end - defp prepare_nft(nft, token, need_uniqueness?) do + defp prepare_nft(nft, token) do Map.merge( %{"token_type" => token.type, "value" => value(token.type, nft)}, - TokenView.prepare_token_instance(nft, token, need_uniqueness?) + TokenView.prepare_token_instance(nft, token) ) end @@ -199,7 +209,7 @@ defmodule BlockScoutWeb.API.V2.AddressView do defp prepare_nft_for_collection(token_type, instance) do Map.merge( %{"token_type" => token_type, "value" => value(token_type, instance)}, - TokenView.prepare_token_instance(instance, nil, false) + TokenView.prepare_token_instance(instance, nil) ) end 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 bc6b62d7c712..9c4b40a3516f 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 @@ -3,7 +3,6 @@ defmodule BlockScoutWeb.API.V2.BlockView do alias BlockScoutWeb.BlockView alias BlockScoutWeb.API.V2.{ApiView, Helper} - alias Explorer.Chain alias Explorer.Chain.Block alias Explorer.Counters.BlockPriorityFeeCounter @@ -29,10 +28,10 @@ defmodule BlockScoutWeb.API.V2.BlockView do end def prepare_block(block, _conn, single_block? \\ false) do - burned_fee = Chain.burned_fees(block.transactions, block.base_fee_per_gas) + burnt_fees = Block.burnt_fees(block.transactions, block.base_fee_per_gas) priority_fee = block.base_fee_per_gas && BlockPriorityFeeCounter.fetch(block.hash) - tx_fees = Chain.txn_fees(block.transactions) + transaction_fees = Block.transaction_fees(block.transactions) %{ "height" => block.number, @@ -48,7 +47,7 @@ defmodule BlockScoutWeb.API.V2.BlockView do "gas_limit" => block.gas_limit, "nonce" => block.nonce, "base_fee_per_gas" => block.base_fee_per_gas, - "burnt_fees" => burned_fee, + "burnt_fees" => burnt_fees, "priority_fee" => priority_fee, # "extra_data" => "TODO", "uncles_hashes" => prepare_uncles(block.uncle_relations), @@ -56,9 +55,9 @@ defmodule BlockScoutWeb.API.V2.BlockView do "rewards" => prepare_rewards(block.rewards, block, single_block?), "gas_target_percentage" => gas_target(block), "gas_used_percentage" => gas_used_percentage(block), - "burnt_fees_percentage" => burnt_fees_percentage(burned_fee, tx_fees), + "burnt_fees_percentage" => burnt_fees_percentage(burnt_fees, transaction_fees), "type" => block |> BlockView.block_type() |> String.downcase(), - "tx_fees" => tx_fees, + "tx_fees" => transaction_fees, "withdrawals_count" => count_withdrawals(block) } |> chain_type_fields(block, single_block?) @@ -105,8 +104,9 @@ defmodule BlockScoutWeb.API.V2.BlockView do def burnt_fees_percentage(_, %Decimal{coef: 0}), do: nil - def burnt_fees_percentage(burnt_fees, tx_fees) when not is_nil(tx_fees) and not is_nil(burnt_fees) do - burnt_fees.value |> Decimal.div(tx_fees) |> Decimal.mult(100) |> Decimal.to_float() + def burnt_fees_percentage(burnt_fees, transaction_fees) + when not is_nil(transaction_fees) and not is_nil(burnt_fees) do + burnt_fees.value |> Decimal.div(transaction_fees) |> Decimal.mult(100) |> Decimal.to_float() end def burnt_fees_percentage(_, _), do: nil diff --git a/apps/block_scout_web/lib/block_scout_web/views/api/v2/helper.ex b/apps/block_scout_web/lib/block_scout_web/views/api/v2/helper.ex index 9b82e01ede77..96a69840ee24 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/api/v2/helper.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/api/v2/helper.ex @@ -51,13 +51,20 @@ defmodule BlockScoutWeb.API.V2.Helper do defp address_with_info(%Address{} = address, _address_hash) do %{ "hash" => Address.checksum(address), - "is_contract" => is_smart_contract(address), + "is_contract" => Address.is_smart_contract(address), "name" => address_name(address), "implementation_name" => implementation_name(address), - "is_verified" => is_verified(address) + "is_verified" => is_verified(address), + "ens_domain_name" => address.ens_domain_name } end + defp address_with_info(%{ens_domain_name: name}, address_hash) do + nil + |> address_with_info(address_hash) + |> Map.put("ens_domain_name", name) + end + defp address_with_info(%NotLoaded{}, address_hash) do address_with_info(nil, address_hash) end @@ -94,11 +101,6 @@ defmodule BlockScoutWeb.API.V2.Helper do def implementation_name(_), do: nil - def is_smart_contract(%Address{contract_code: nil}), do: false - def is_smart_contract(%Address{contract_code: _}), do: true - def is_smart_contract(%NotLoaded{}), do: nil - def is_smart_contract(_), do: false - def is_verified(%Address{smart_contract: nil}), do: false def is_verified(%Address{smart_contract: %{metadata_from_verified_twin: true}}), do: false def is_verified(%Address{smart_contract: %NotLoaded{}}), do: nil diff --git a/apps/block_scout_web/lib/block_scout_web/views/api/v2/search_view.ex b/apps/block_scout_web/lib/block_scout_web/views/api/v2/search_view.ex index 0376e04f423e..b56c67352056 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/api/v2/search_view.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/api/v2/search_view.ex @@ -47,7 +47,8 @@ defmodule BlockScoutWeb.API.V2.SearchView do "name" => search_result.name, "address" => search_result.address_hash, "url" => address_path(Endpoint, :show, search_result.address_hash), - "is_smart_contract_verified" => search_result.verified + "is_smart_contract_verified" => search_result.verified, + "ens_info" => search_result[:ens_info] } end diff --git a/apps/block_scout_web/lib/block_scout_web/views/api/v2/token_view.ex b/apps/block_scout_web/lib/block_scout_web/views/api/v2/token_view.ex index 681c5fa3418e..616299fde941 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/api/v2/token_view.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/api/v2/token_view.ex @@ -4,13 +4,9 @@ defmodule BlockScoutWeb.API.V2.TokenView do alias BlockScoutWeb.API.V2.Helper alias BlockScoutWeb.NFTHelper alias Ecto.Association.NotLoaded - alias Explorer.Chain alias Explorer.Chain.Address - alias Explorer.Chain.Address.CurrentTokenBalance alias Explorer.Chain.Token.Instance - @api_true [api?: true] - def render("token.json", %{token: nil, contract_address_hash: contract_address_hash}) do %{ "address" => Address.checksum(contract_address_hash), @@ -90,18 +86,16 @@ defmodule BlockScoutWeb.API.V2.TokenView do @doc """ Internal json rendering function """ - def prepare_token_instance(instance, token, need_uniqueness_and_owner? \\ true) do - is_unique = is_unique?(need_uniqueness_and_owner?, instance, token) - + def prepare_token_instance(instance, token) do %{ "id" => instance.token_id, "metadata" => instance.metadata, - "owner" => token_instance_owner(is_unique, instance), + "owner" => token_instance_owner(instance.is_unique, instance), "token" => render("token.json", %{token: token}), "external_app_url" => NFTHelper.external_url(instance), "animation_url" => instance.metadata && NFTHelper.retrieve_image(instance.metadata["animation_url"]), "image_url" => instance.metadata && NFTHelper.get_media_src(instance.metadata, false), - "is_unique" => is_unique + "is_unique" => instance.is_unique } end @@ -117,29 +111,6 @@ defmodule BlockScoutWeb.API.V2.TokenView do defp token_instance_owner(_is_unique, instance), do: instance.owner && Helper.address_with_info(nil, instance.owner, instance.owner.hash, false) - defp is_unique?(false, _instance, _token), do: nil - - defp is_unique?( - not_ignore?, - %Instance{current_token_balance: %CurrentTokenBalance{value: %Decimal{} = value}} = instance, - token - ) do - if Decimal.compare(value, 1) == :gt do - false - else - is_unique?(not_ignore?, %Instance{instance | current_token_balance: nil}, token) - end - end - - defp is_unique?(_not_ignore?, %Instance{current_token_balance: %CurrentTokenBalance{value: value}}, _token) - when value > 1, - do: false - - defp is_unique?(_, instance, token), - do: - not (token.type == "ERC-1155") or - Chain.token_id_1155_is_unique?(token.contract_address_hash, instance.token_id, @api_true) - defp prepare_holders_count(nil), do: nil defp prepare_holders_count(count) when count < 0, do: prepare_holders_count(0) defp prepare_holders_count(count), do: to_string(count) diff --git a/apps/block_scout_web/lib/block_scout_web/views/api/v2/transaction_view.ex b/apps/block_scout_web/lib/block_scout_web/views/api/v2/transaction_view.ex index f0131689a372..1d9be47026c0 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/api/v2/transaction_view.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/api/v2/transaction_view.ex @@ -184,6 +184,10 @@ defmodule BlockScoutWeb.API.V2.TransactionView do } end + @doc """ + Decodes list of logs + """ + @spec decode_logs([Log.t()], boolean) :: [tuple] def decode_logs(logs, skip_sig_provider?) do {result, _, _} = Enum.reduce(logs, {[], %{}, %{}}, fn log, {results, contracts_acc, events_acc} -> @@ -204,12 +208,15 @@ defmodule BlockScoutWeb.API.V2.TransactionView do end def decode_transactions(transactions, skip_sig_provider?) do - Enum.reduce(transactions, {[], %{}, %{}}, fn transaction, {results, abi_acc, methods_acc} -> - {result, abi_acc, methods_acc} = - Transaction.decoded_input_data(transaction, skip_sig_provider?, @api_true, abi_acc, methods_acc) + {results, abi_acc, methods_acc} = + Enum.reduce(transactions, {[], %{}, %{}}, fn transaction, {results, abi_acc, methods_acc} -> + {result, abi_acc, methods_acc} = + Transaction.decoded_input_data(transaction, skip_sig_provider?, @api_true, abi_acc, methods_acc) - {Enum.reverse([format_decoded_input(result) | Enum.reverse(results)]), abi_acc, methods_acc} - end) + {[format_decoded_input(result) | results], abi_acc, methods_acc} + end) + + {Enum.reverse(results), abi_acc, methods_acc} end def prepare_token_transfer(token_transfer, _conn, decoded_input) do @@ -283,12 +290,12 @@ defmodule BlockScoutWeb.API.V2.TransactionView do } end - def prepare_log(log, transaction_or_hash, decoded_log) do + def prepare_log(log, transaction_or_hash, decoded_log, tags_for_address_needed? \\ false) do decoded = process_decoded_log(decoded_log) %{ "tx_hash" => get_tx_hash(transaction_or_hash), - "address" => Helper.address_with_info(nil, log.address, log.address_hash, false), + "address" => Helper.address_with_info(nil, log.address, log.address_hash, tags_for_address_needed?), "topics" => [ log.first_topic, log.second_topic, @@ -355,7 +362,7 @@ defmodule BlockScoutWeb.API.V2.TransactionView do priority_fee_per_gas = priority_fee_per_gas(max_priority_fee_per_gas, base_fee_per_gas, max_fee_per_gas) - burned_fee = burned_fee(transaction, max_fee_per_gas, base_fee_per_gas) + burnt_fees = burnt_fees(transaction, max_fee_per_gas, base_fee_per_gas) status = transaction |> Chain.transaction_to_status() |> format_status() @@ -368,7 +375,7 @@ defmodule BlockScoutWeb.API.V2.TransactionView do "result" => status, "status" => transaction.status, "block" => transaction.block_number, - "timestamp" => block_timestamp(transaction.block), + "timestamp" => block_timestamp(transaction), "from" => Helper.address_with_info( single_tx? && conn, @@ -405,7 +412,7 @@ defmodule BlockScoutWeb.API.V2.TransactionView do "max_priority_fee_per_gas" => transaction.max_priority_fee_per_gas, "base_fee_per_gas" => base_fee_per_gas, "priority_fee" => priority_fee_per_gas && Wei.mult(priority_fee_per_gas, transaction.gas_used), - "tx_burnt_fee" => burned_fee, + "tx_burnt_fee" => burnt_fees, "nonce" => transaction.nonce, "position" => transaction.index, "revert_reason" => revert_reason, @@ -575,9 +582,12 @@ defmodule BlockScoutWeb.API.V2.TransactionView do def token_transfers_overflow(token_transfers, _), do: Enum.count(token_transfers) > Chain.get_token_transfers_per_transaction_preview_count() - defp transaction_actions(%NotLoaded{}), do: [] + def transaction_actions(%NotLoaded{}), do: [] - defp transaction_actions(actions) do + @doc """ + Renders transaction actions + """ + def transaction_actions(actions) do render("transaction_actions.json", %{actions: actions}) end @@ -590,7 +600,7 @@ defmodule BlockScoutWeb.API.V2.TransactionView do end) end - defp burned_fee(transaction, max_fee_per_gas, base_fee_per_gas) do + defp burnt_fees(transaction, max_fee_per_gas, base_fee_per_gas) do if !is_nil(max_fee_per_gas) and !is_nil(transaction.gas_used) and !is_nil(base_fee_per_gas) do if Decimal.compare(max_fee_per_gas.value, 0) == :eq do %Wei{value: Decimal.new(0)} @@ -619,7 +629,11 @@ defmodule BlockScoutWeb.API.V2.TransactionView do end end - defp decoded_input(decoded_input) do + @doc """ + Prepares decoded tx info + """ + @spec decoded_input(any()) :: map() | nil + def decoded_input(decoded_input) do case decoded_input do {:ok, method_id, text, mapping} -> render(__MODULE__, "decoded_input.json", method_id: method_id, text: text, mapping: mapping, error?: false) @@ -644,13 +658,13 @@ defmodule BlockScoutWeb.API.V2.TransactionView do defp format_status({:error, reason}), do: reason defp format_status(status), do: status - defp format_decoded_input({:error, _, []}), do: nil - defp format_decoded_input({:error, _, candidates}), do: Enum.at(candidates, 0) - defp format_decoded_input({:ok, _identifier, _text, _mapping} = decoded), do: decoded - defp format_decoded_input(_), do: nil + @spec format_decoded_input(any()) :: nil | map() | tuple() + def format_decoded_input({:error, _, []}), do: nil + def format_decoded_input({:error, _, candidates}), do: Enum.at(candidates, 0) + def format_decoded_input({:ok, _identifier, _text, _mapping} = decoded), do: decoded + def format_decoded_input(_), do: nil defp format_decoded_log_input({:error, :could_not_decode}), do: nil - defp format_decoded_log_input({:error, :no_matching_function}), do: nil defp format_decoded_log_input({:ok, _method_id, _text, _mapping} = decoded), do: decoded defp format_decoded_log_input({:error, _, candidates}), do: Enum.at(candidates, 0) @@ -698,31 +712,51 @@ defmodule BlockScoutWeb.API.V2.TransactionView do |> Timex.diff(right, :milliseconds) end - defp method_name(_, _, skip_sc_check? \\ false) + @doc """ + Return method name used in tx + """ + @spec method_name(Transaction.t(), any(), boolean()) :: binary() | nil + def method_name(_, _, skip_sc_check? \\ false) - defp method_name(_, {:ok, _method_id, text, _mapping}, _) do + def method_name(_, {:ok, _method_id, text, _mapping}, _) do Transaction.parse_method_name(text, false) end - defp method_name( - %Transaction{to_address: to_address, input: %{bytes: <>}}, - _, - skip_sc_check? - ) do - if skip_sc_check? || Helper.is_smart_contract(to_address) do + def method_name( + %Transaction{to_address: to_address, input: %{bytes: <>}}, + _, + skip_sc_check? + ) do + if skip_sc_check? || Address.is_smart_contract(to_address) do "0x" <> Base.encode16(method_id, case: :lower) else nil end end - defp method_name(_, _, _) do + def method_name(_, _, _) do nil end - defp tx_types(tx, types \\ [], stage \\ :token_transfer) - - defp tx_types(%Transaction{token_transfers: token_transfers} = tx, types, :token_transfer) do + @doc """ + Returns array of token types for tx. + """ + @spec tx_types( + Explorer.Chain.Transaction.t(), + [tx_type], + tx_type + ) :: [tx_type] + when tx_type: + :coin_transfer + | :contract_call + | :contract_creation + | :rootstock_bridge + | :rootstock_remasc + | :token_creation + | :token_transfer + def tx_types(tx, types \\ [], stage \\ :token_transfer) + + def tx_types(%Transaction{token_transfers: token_transfers} = tx, types, :token_transfer) do types = if (!is_nil(token_transfers) && token_transfers != [] && !match?(%NotLoaded{}, token_transfers)) || tx.has_token_transfers do @@ -734,7 +768,7 @@ defmodule BlockScoutWeb.API.V2.TransactionView do tx_types(tx, types, :token_creation) end - defp tx_types(%Transaction{created_contract_address: created_contract_address} = tx, types, :token_creation) do + def tx_types(%Transaction{created_contract_address: created_contract_address} = tx, types, :token_creation) do types = if match?(%Address{}, created_contract_address) && match?(%Token{}, created_contract_address.token) do [:token_creation | types] @@ -745,11 +779,11 @@ defmodule BlockScoutWeb.API.V2.TransactionView do tx_types(tx, types, :contract_creation) end - defp tx_types( - %Transaction{to_address_hash: to_address_hash} = tx, - types, - :contract_creation - ) do + def tx_types( + %Transaction{to_address_hash: to_address_hash} = tx, + types, + :contract_creation + ) do types = if is_nil(to_address_hash) do [:contract_creation | types] @@ -760,9 +794,9 @@ defmodule BlockScoutWeb.API.V2.TransactionView do tx_types(tx, types, :contract_call) end - defp tx_types(%Transaction{to_address: to_address} = tx, types, :contract_call) do + def tx_types(%Transaction{to_address: to_address} = tx, types, :contract_call) do types = - if Helper.is_smart_contract(to_address) do + if Address.is_smart_contract(to_address) do [:contract_call | types] else types @@ -771,7 +805,7 @@ defmodule BlockScoutWeb.API.V2.TransactionView do tx_types(tx, types, :coin_transfer) end - defp tx_types(%Transaction{value: value} = tx, types, :coin_transfer) do + def tx_types(%Transaction{value: value} = tx, types, :coin_transfer) do types = if Decimal.compare(value.value, 0) == :gt do [:coin_transfer | types] @@ -782,7 +816,7 @@ defmodule BlockScoutWeb.API.V2.TransactionView do tx_types(tx, types, :rootstock_remasc) end - defp tx_types(tx, types, :rootstock_remasc) do + def tx_types(tx, types, :rootstock_remasc) do types = if Transaction.is_rootstock_remasc_transaction(tx) do [:rootstock_remasc | types] @@ -793,7 +827,7 @@ defmodule BlockScoutWeb.API.V2.TransactionView do tx_types(tx, types, :rootstock_bridge) end - defp tx_types(tx, types, :rootstock_bridge) do + def tx_types(tx, types, :rootstock_bridge) do if Transaction.is_rootstock_bridge_transaction(tx) do [:rootstock_bridge | types] else @@ -801,6 +835,7 @@ defmodule BlockScoutWeb.API.V2.TransactionView do end end + defp block_timestamp(%Transaction{block_timestamp: block_ts}) when not is_nil(block_ts), do: block_ts defp block_timestamp(%Transaction{block: %Block{} = block}), do: block.timestamp defp block_timestamp(%Block{} = block), do: block.timestamp defp block_timestamp(_), do: nil diff --git a/apps/block_scout_web/lib/block_scout_web/views/block_view.ex b/apps/block_scout_web/lib/block_scout_web/views/block_view.ex index e6355faf1945..2f215b478ced 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/block_view.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/block_view.ex @@ -7,7 +7,7 @@ defmodule BlockScoutWeb.BlockView do alias Explorer.Chain alias Explorer.Chain.{Block, Wei} alias Explorer.Chain.Block.Reward - alias Explorer.Counters.{BlockBurnedFeeCounter, BlockPriorityFeeCounter} + alias Explorer.Counters.{BlockBurntFeeCounter, BlockPriorityFeeCounter} @dialyzer :no_match diff --git a/apps/block_scout_web/lib/block_scout_web/views/chain_view.ex b/apps/block_scout_web/lib/block_scout_web/views/chain_view.ex index 43d66a29266d..bbbcdebe866e 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/chain_view.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/chain_view.ex @@ -60,10 +60,7 @@ defmodule BlockScoutWeb.ChainView do defp gas_prices do case GasPriceOracle.get_gas_prices() do {:ok, gas_prices} -> - gas_prices - - nil -> - nil + %{slow: gas_prices[:slow][:price], average: gas_prices[:average][:price], fast: gas_prices[:fast][:price]} _ -> nil diff --git a/apps/block_scout_web/lib/block_scout_web/views/robots_view.ex b/apps/block_scout_web/lib/block_scout_web/views/robots_view.ex index 20e4cca0596f..9939ec3e684f 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/robots_view.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/robots_view.ex @@ -3,7 +3,7 @@ defmodule BlockScoutWeb.RobotsView do alias BlockScoutWeb.APIDocsView alias Explorer.{Chain, PagingOptions} - alias Explorer.Chain.Address + alias Explorer.Chain.{Address, Token} @limit 200 defp limit, do: @limit diff --git a/apps/block_scout_web/lib/block_scout_web/views/transaction_view.ex b/apps/block_scout_web/lib/block_scout_web/views/transaction_view.ex index 548903cdd583..ae493f391341 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/transaction_view.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/transaction_view.ex @@ -32,11 +32,13 @@ defmodule BlockScoutWeb.TransactionView do defdelegate formatted_timestamp(block), to: BlockView def block_number(%Transaction{block_number: nil}), do: gettext("Block Pending") - def block_number(%Transaction{block: block}), do: [view_module: BlockView, partial: "_link.html", block: block] + + def block_number(%Transaction{block_number: number, block_hash: hash}), + do: [view_module: BlockView, partial: "_link.html", block: %Block{number: number, hash: hash}] + def block_number(%Reward{block: block}), do: [view_module: BlockView, partial: "_link.html", block: block] - def block_timestamp(%Transaction{block_number: nil, inserted_at: time}), do: time - def block_timestamp(%Transaction{block: %Block{timestamp: time}}), do: time + def block_timestamp(%Transaction{} = transaction), do: Transaction.block_timestamp(transaction) def block_timestamp(%Reward{block: %Block{timestamp: time}}), do: time def value_transfer?(%Transaction{input: %{bytes: bytes}}) when bytes in [<<>>, nil] do diff --git a/apps/block_scout_web/lib/block_scout_web/views/wei_helper.ex b/apps/block_scout_web/lib/block_scout_web/views/wei_helper.ex index 3b507d46557c..72c1c1ee1f3a 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/wei_helper.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/wei_helper.ex @@ -52,8 +52,12 @@ defmodule BlockScoutWeb.WeiHelper do ...> ) "10" """ - @spec format_wei_value(Wei.t(), Wei.unit(), format_options()) :: String.t() - def format_wei_value(%Wei{} = wei, unit, options \\ []) when unit in @valid_units do + @spec format_wei_value(Wei.t() | nil, Wei.unit(), format_options()) :: String.t() | nil + def format_wei_value(_wei, _unit, _options \\ []) + + def format_wei_value(nil, _unit, _options), do: nil + + def format_wei_value(%Wei{} = wei, unit, options) when unit in @valid_units do converted_value = wei |> Wei.to(unit) diff --git a/apps/block_scout_web/mix.exs b/apps/block_scout_web/mix.exs index f134b2b3373f..e90612b58202 100644 --- a/apps/block_scout_web/mix.exs +++ b/apps/block_scout_web/mix.exs @@ -23,7 +23,7 @@ defmodule BlockScoutWeb.Mixfile do dialyzer: :test ], start_permanent: Mix.env() == :prod, - version: "5.3.2", + version: "6.0.0", xref: [exclude: [Explorer.Chain.Zkevm.Reader]] ] end @@ -84,7 +84,7 @@ defmodule BlockScoutWeb.Mixfile do # HTML CSS selectors for Phoenix controller tests {:floki, "~> 0.31"}, {:flow, "~> 1.2"}, - {:gettext, "~> 0.23.1"}, + {:gettext, "~> 0.24.0"}, {:hammer, "~> 6.0"}, {:httpoison, "~> 2.0"}, {:indexer, in_umbrella: true, runtime: false}, diff --git a/apps/block_scout_web/priv/gettext/default.pot b/apps/block_scout_web/priv/gettext/default.pot index d1902773008b..224aa1df7789 100644 --- a/apps/block_scout_web/priv/gettext/default.pot +++ b/apps/block_scout_web/priv/gettext/default.pot @@ -68,7 +68,7 @@ msgstr "" msgid "%{subnetwork} Explorer - BlockScout" msgstr "" -#: lib/block_scout_web/views/transaction_view.ex:369 +#: lib/block_scout_web/views/transaction_view.ex:371 #, elixir-autogen, elixir-format msgid "(Awaiting internal transactions for status)" msgstr "" @@ -86,7 +86,7 @@ msgstr "" msgid ") may be added for each contract. Click the Add Library button to add an additional one." msgstr "" -#: lib/block_scout_web/templates/layout/app.html.eex:90 +#: lib/block_scout_web/templates/layout/app.html.eex:93 #, elixir-autogen, elixir-format msgid "- We're indexing this chain right now. Some of the counts may be inaccurate." msgstr "" @@ -122,7 +122,7 @@ msgstr "" msgid "A block producer who successfully included the block onto the blockchain." msgstr "" -#: lib/block_scout_web/templates/layout/app.html.eex:97 +#: lib/block_scout_web/templates/layout/app.html.eex:100 #, elixir-autogen, elixir-format msgid "A confirmation email was sent to" msgstr "" @@ -698,7 +698,7 @@ msgstr "" msgid "Compiler version" msgstr "" -#: lib/block_scout_web/views/transaction_view.ex:362 +#: lib/block_scout_web/views/transaction_view.ex:364 #, elixir-autogen, elixir-format msgid "Confirmed" msgstr "" @@ -783,12 +783,12 @@ msgstr "" msgid "Contract Address Pending" msgstr "" -#: lib/block_scout_web/views/transaction_view.ex:478 +#: lib/block_scout_web/views/transaction_view.ex:480 #, elixir-autogen, elixir-format msgid "Contract Call" msgstr "" -#: lib/block_scout_web/views/transaction_view.ex:475 +#: lib/block_scout_web/views/transaction_view.ex:477 #, elixir-autogen, elixir-format msgid "Contract Creation" msgstr "" @@ -1049,7 +1049,7 @@ msgstr "" msgid "Daily Transactions" msgstr "" -#: lib/block_scout_web/templates/address_logs/_logs.html.eex:101 +#: lib/block_scout_web/templates/address_logs/_logs.html.eex:98 #: lib/block_scout_web/templates/log/_data_decoded_view.html.eex:7 #: lib/block_scout_web/templates/transaction/_decoded_input_body.html.eex:23 #: lib/block_scout_web/templates/transaction_log/_logs.html.eex:121 @@ -1186,12 +1186,12 @@ msgstr "" msgid "EIP-1167" msgstr "" -#: lib/block_scout_web/views/transaction_view.ex:220 +#: lib/block_scout_web/views/transaction_view.ex:222 #, elixir-autogen, elixir-format msgid "ERC-1155 " msgstr "" -#: lib/block_scout_web/views/transaction_view.ex:218 +#: lib/block_scout_web/views/transaction_view.ex:220 #, elixir-autogen, elixir-format msgid "ERC-20 " msgstr "" @@ -1201,7 +1201,7 @@ msgstr "" msgid "ERC-20 tokens (beta)" msgstr "" -#: lib/block_scout_web/views/transaction_view.ex:219 +#: lib/block_scout_web/views/transaction_view.ex:221 #, elixir-autogen, elixir-format msgid "ERC-721 " msgstr "" @@ -1292,12 +1292,12 @@ msgstr "" msgid "Error trying to fetch balances." msgstr "" -#: lib/block_scout_web/views/transaction_view.ex:373 +#: lib/block_scout_web/views/transaction_view.ex:375 #, elixir-autogen, elixir-format msgid "Error: %{reason}" msgstr "" -#: lib/block_scout_web/views/transaction_view.ex:371 +#: lib/block_scout_web/views/transaction_view.ex:373 #, elixir-autogen, elixir-format msgid "Error: (Awaiting internal transactions for reason)" msgstr "" @@ -1485,7 +1485,7 @@ msgstr "" #: lib/block_scout_web/templates/chain/gas_price_oracle_legend_item.html.eex:22 #: lib/block_scout_web/templates/chain/gas_price_oracle_legend_item.html.eex:38 #: lib/block_scout_web/views/block_view.ex:22 -#: lib/block_scout_web/views/wei_helper.ex:77 +#: lib/block_scout_web/views/wei_helper.ex:81 #, elixir-autogen, elixir-format msgid "Gwei" msgstr "" @@ -1602,7 +1602,7 @@ msgstr "" #: lib/block_scout_web/templates/transaction/_tabs.html.eex:11 #: lib/block_scout_web/templates/transaction_internal_transaction/index.html.eex:6 #: lib/block_scout_web/views/address_view.ex:376 -#: lib/block_scout_web/views/transaction_view.ex:533 +#: lib/block_scout_web/views/transaction_view.ex:535 #, elixir-autogen, elixir-format msgid "Internal Transactions" msgstr "" @@ -1719,7 +1719,7 @@ msgstr "" #: lib/block_scout_web/templates/transaction/_tabs.html.eex:17 #: lib/block_scout_web/templates/transaction_log/index.html.eex:8 #: lib/block_scout_web/views/address_view.ex:387 -#: lib/block_scout_web/views/transaction_view.ex:534 +#: lib/block_scout_web/views/transaction_view.ex:536 #, elixir-autogen, elixir-format msgid "Logs" msgstr "" @@ -1752,7 +1752,7 @@ msgstr "" msgid "Max Priority Fee per Gas" msgstr "" -#: lib/block_scout_web/views/transaction_view.ex:325 +#: lib/block_scout_web/views/transaction_view.ex:327 #, elixir-autogen, elixir-format msgid "Max of" msgstr "" @@ -2064,8 +2064,8 @@ msgid "Parent Hash" msgstr "" #: lib/block_scout_web/templates/layout/_topnav.html.eex:63 -#: lib/block_scout_web/views/transaction_view.ex:368 -#: lib/block_scout_web/views/transaction_view.ex:407 +#: lib/block_scout_web/views/transaction_view.ex:370 +#: lib/block_scout_web/views/transaction_view.ex:409 #, elixir-autogen, elixir-format msgid "Pending" msgstr "" @@ -2082,7 +2082,7 @@ msgid "Play" msgstr "" #: lib/block_scout_web/templates/layout/_account_menu_item.html.eex:22 -#: lib/block_scout_web/templates/layout/app.html.eex:97 +#: lib/block_scout_web/templates/layout/app.html.eex:100 #, elixir-autogen, elixir-format msgid "Please confirm your email address to use the My Account feature." msgstr "" @@ -2201,7 +2201,7 @@ msgstr "" #: lib/block_scout_web/templates/transaction/_tabs.html.eex:24 #: lib/block_scout_web/templates/transaction_raw_trace/_card_body.html.eex:1 -#: lib/block_scout_web/views/transaction_view.ex:535 +#: lib/block_scout_web/views/transaction_view.ex:537 #, elixir-autogen, elixir-format msgid "Raw Trace" msgstr "" @@ -2262,7 +2262,7 @@ msgstr "" msgid "Request to edit a public tag/label" msgstr "" -#: lib/block_scout_web/templates/layout/app.html.eex:97 +#: lib/block_scout_web/templates/layout/app.html.eex:100 #, elixir-autogen, elixir-format msgid "Resend verification email" msgstr "" @@ -2514,7 +2514,7 @@ msgstr "" #: lib/block_scout_web/templates/transaction/_tabs.html.eex:29 #: lib/block_scout_web/templates/transaction_state/index.html.eex:6 -#: lib/block_scout_web/views/transaction_view.ex:536 +#: lib/block_scout_web/views/transaction_view.ex:538 #, elixir-autogen, elixir-format msgid "State changes" msgstr "" @@ -2540,7 +2540,7 @@ msgid "Submit an Issue" msgstr "" #: lib/block_scout_web/templates/transaction/_emission_reward_tile.html.eex:8 -#: lib/block_scout_web/views/transaction_view.ex:370 +#: lib/block_scout_web/views/transaction_view.ex:372 #, elixir-autogen, elixir-format msgid "Success" msgstr "" @@ -2849,13 +2849,13 @@ msgid "Token" msgstr "" #: lib/block_scout_web/templates/common_components/_token_transfer_type_display_name.html.eex:3 -#: lib/block_scout_web/views/transaction_view.ex:469 +#: lib/block_scout_web/views/transaction_view.ex:471 #, elixir-autogen, elixir-format msgid "Token Burning" msgstr "" #: lib/block_scout_web/templates/common_components/_token_transfer_type_display_name.html.eex:7 -#: lib/block_scout_web/views/transaction_view.ex:470 +#: lib/block_scout_web/views/transaction_view.ex:472 #, elixir-autogen, elixir-format msgid "Token Creation" msgstr "" @@ -2883,14 +2883,14 @@ msgid "Token ID" msgstr "" #: lib/block_scout_web/templates/common_components/_token_transfer_type_display_name.html.eex:5 -#: lib/block_scout_web/views/transaction_view.ex:468 +#: lib/block_scout_web/views/transaction_view.ex:470 #, elixir-autogen, elixir-format msgid "Token Minting" msgstr "" #: lib/block_scout_web/templates/common_components/_token_transfer_type_display_name.html.eex:9 #: lib/block_scout_web/templates/common_components/_token_transfer_type_display_name.html.eex:11 -#: lib/block_scout_web/views/transaction_view.ex:471 +#: lib/block_scout_web/views/transaction_view.ex:473 #, elixir-autogen, elixir-format msgid "Token Transfer" msgstr "" @@ -2906,7 +2906,7 @@ msgstr "" #: lib/block_scout_web/views/address_view.ex:378 #: lib/block_scout_web/views/tokens/instance/overview_view.ex:114 #: lib/block_scout_web/views/tokens/overview_view.ex:40 -#: lib/block_scout_web/views/transaction_view.ex:532 +#: lib/block_scout_web/views/transaction_view.ex:534 #, elixir-autogen, elixir-format msgid "Token Transfers" msgstr "" @@ -2962,7 +2962,7 @@ msgstr "" msgid "Topic" msgstr "" -#: lib/block_scout_web/templates/address_logs/_logs.html.eex:71 +#: lib/block_scout_web/templates/address_logs/_logs.html.eex:68 #: lib/block_scout_web/templates/transaction_log/_logs.html.eex:91 #, elixir-autogen, elixir-format msgid "Topics" @@ -3022,7 +3022,7 @@ msgstr "" #: lib/block_scout_web/templates/account/tag_transaction/form.html.eex:11 #: lib/block_scout_web/templates/account/tag_transaction/index.html.eex:23 #: lib/block_scout_web/templates/address_logs/_logs.html.eex:19 -#: lib/block_scout_web/views/transaction_view.ex:481 +#: lib/block_scout_web/views/transaction_view.ex:483 #, elixir-autogen, elixir-format msgid "Transaction" msgstr "" @@ -3177,7 +3177,7 @@ msgstr "" msgid "Uncles" msgstr "" -#: lib/block_scout_web/views/transaction_view.ex:361 +#: lib/block_scout_web/views/transaction_view.ex:363 #, elixir-autogen, elixir-format msgid "Unconfirmed" msgstr "" @@ -3444,7 +3444,7 @@ msgstr "" msgid "We recommend using flattened code. This is necessary if your code utilizes a library or inherits dependencies. Use the" msgstr "" -#: lib/block_scout_web/views/wei_helper.ex:76 +#: lib/block_scout_web/views/wei_helper.ex:80 #, elixir-autogen, elixir-format msgid "Wei" msgstr "" @@ -3541,16 +3541,6 @@ msgstr "" msgid "balance of the address" msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:437 -#, elixir-autogen, elixir-format -msgid "burned for this transaction. Equals Block Base Fee per Gas * Gas Used." -msgstr "" - -#: lib/block_scout_web/templates/block/overview.html.eex:215 -#, elixir-autogen, elixir-format -msgid "burned from transactions included in the block (Base fee (per unit of gas) * Gas Used)." -msgstr "" - #: lib/block_scout_web/templates/address_contract/index.html.eex:28 #, elixir-autogen, elixir-format msgid "button" @@ -3581,7 +3571,7 @@ msgstr "" msgid "fallback" msgstr "" -#: lib/block_scout_web/views/address_contract_view.ex:28 +#: lib/block_scout_web/views/address_contract_view.ex:30 #, elixir-autogen, elixir-format msgid "false" msgstr "" @@ -3608,7 +3598,7 @@ msgstr "" msgid "of" msgstr "" -#: lib/block_scout_web/templates/layout/app.html.eex:97 +#: lib/block_scout_web/templates/layout/app.html.eex:100 #, elixir-autogen, elixir-format msgid "on sign up. Didn’t receive?" msgstr "" @@ -3637,7 +3627,7 @@ msgstr "" msgid "string" msgstr "" -#: lib/block_scout_web/views/address_contract_view.ex:27 +#: lib/block_scout_web/views/address_contract_view.ex:29 #, elixir-autogen, elixir-format msgid "true" msgstr "" @@ -3682,3 +3672,13 @@ msgstr "" #, elixir-autogen, elixir-format msgid "Yul" msgstr "" + +#: lib/block_scout_web/templates/transaction/overview.html.eex:437 +#, elixir-autogen, elixir-format +msgid "burnt for this transaction. Equals Block Base Fee per Gas * Gas Used." +msgstr "" + +#: lib/block_scout_web/templates/block/overview.html.eex:215 +#, elixir-autogen, elixir-format +msgid "burnt from transactions included in the block (Base fee (per unit of gas) * Gas Used)." +msgstr "" diff --git a/apps/block_scout_web/priv/gettext/en/LC_MESSAGES/default.po b/apps/block_scout_web/priv/gettext/en/LC_MESSAGES/default.po index c3c3d7628b7a..4d1362f4d282 100644 --- a/apps/block_scout_web/priv/gettext/en/LC_MESSAGES/default.po +++ b/apps/block_scout_web/priv/gettext/en/LC_MESSAGES/default.po @@ -68,7 +68,7 @@ msgstr "" msgid "%{subnetwork} Explorer - BlockScout" msgstr "" -#: lib/block_scout_web/views/transaction_view.ex:369 +#: lib/block_scout_web/views/transaction_view.ex:371 #, elixir-autogen, elixir-format msgid "(Awaiting internal transactions for status)" msgstr "" @@ -86,7 +86,7 @@ msgstr "" msgid ") may be added for each contract. Click the Add Library button to add an additional one." msgstr "" -#: lib/block_scout_web/templates/layout/app.html.eex:90 +#: lib/block_scout_web/templates/layout/app.html.eex:93 #, elixir-autogen, elixir-format msgid "- We're indexing this chain right now. Some of the counts may be inaccurate." msgstr "" @@ -122,7 +122,7 @@ msgstr "" msgid "A block producer who successfully included the block onto the blockchain." msgstr "" -#: lib/block_scout_web/templates/layout/app.html.eex:97 +#: lib/block_scout_web/templates/layout/app.html.eex:100 #, elixir-autogen, elixir-format msgid "A confirmation email was sent to" msgstr "" @@ -698,7 +698,7 @@ msgstr "" msgid "Compiler version" msgstr "" -#: lib/block_scout_web/views/transaction_view.ex:362 +#: lib/block_scout_web/views/transaction_view.ex:364 #, elixir-autogen, elixir-format msgid "Confirmed" msgstr "" @@ -783,12 +783,12 @@ msgstr "" msgid "Contract Address Pending" msgstr "" -#: lib/block_scout_web/views/transaction_view.ex:478 +#: lib/block_scout_web/views/transaction_view.ex:480 #, elixir-autogen, elixir-format msgid "Contract Call" msgstr "" -#: lib/block_scout_web/views/transaction_view.ex:475 +#: lib/block_scout_web/views/transaction_view.ex:477 #, elixir-autogen, elixir-format msgid "Contract Creation" msgstr "" @@ -1049,7 +1049,7 @@ msgstr "" msgid "Daily Transactions" msgstr "" -#: lib/block_scout_web/templates/address_logs/_logs.html.eex:101 +#: lib/block_scout_web/templates/address_logs/_logs.html.eex:98 #: lib/block_scout_web/templates/log/_data_decoded_view.html.eex:7 #: lib/block_scout_web/templates/transaction/_decoded_input_body.html.eex:23 #: lib/block_scout_web/templates/transaction_log/_logs.html.eex:121 @@ -1186,12 +1186,12 @@ msgstr "" msgid "EIP-1167" msgstr "" -#: lib/block_scout_web/views/transaction_view.ex:220 +#: lib/block_scout_web/views/transaction_view.ex:222 #, elixir-autogen, elixir-format msgid "ERC-1155 " msgstr "" -#: lib/block_scout_web/views/transaction_view.ex:218 +#: lib/block_scout_web/views/transaction_view.ex:220 #, elixir-autogen, elixir-format msgid "ERC-20 " msgstr "" @@ -1201,7 +1201,7 @@ msgstr "" msgid "ERC-20 tokens (beta)" msgstr "" -#: lib/block_scout_web/views/transaction_view.ex:219 +#: lib/block_scout_web/views/transaction_view.ex:221 #, elixir-autogen, elixir-format msgid "ERC-721 " msgstr "" @@ -1292,12 +1292,12 @@ msgstr "" msgid "Error trying to fetch balances." msgstr "" -#: lib/block_scout_web/views/transaction_view.ex:373 +#: lib/block_scout_web/views/transaction_view.ex:375 #, elixir-autogen, elixir-format msgid "Error: %{reason}" msgstr "" -#: lib/block_scout_web/views/transaction_view.ex:371 +#: lib/block_scout_web/views/transaction_view.ex:373 #, elixir-autogen, elixir-format msgid "Error: (Awaiting internal transactions for reason)" msgstr "" @@ -1485,7 +1485,7 @@ msgstr "" #: lib/block_scout_web/templates/chain/gas_price_oracle_legend_item.html.eex:22 #: lib/block_scout_web/templates/chain/gas_price_oracle_legend_item.html.eex:38 #: lib/block_scout_web/views/block_view.ex:22 -#: lib/block_scout_web/views/wei_helper.ex:77 +#: lib/block_scout_web/views/wei_helper.ex:81 #, elixir-autogen, elixir-format msgid "Gwei" msgstr "" @@ -1602,7 +1602,7 @@ msgstr "" #: lib/block_scout_web/templates/transaction/_tabs.html.eex:11 #: lib/block_scout_web/templates/transaction_internal_transaction/index.html.eex:6 #: lib/block_scout_web/views/address_view.ex:376 -#: lib/block_scout_web/views/transaction_view.ex:533 +#: lib/block_scout_web/views/transaction_view.ex:535 #, elixir-autogen, elixir-format msgid "Internal Transactions" msgstr "" @@ -1719,7 +1719,7 @@ msgstr "" #: lib/block_scout_web/templates/transaction/_tabs.html.eex:17 #: lib/block_scout_web/templates/transaction_log/index.html.eex:8 #: lib/block_scout_web/views/address_view.ex:387 -#: lib/block_scout_web/views/transaction_view.ex:534 +#: lib/block_scout_web/views/transaction_view.ex:536 #, elixir-autogen, elixir-format msgid "Logs" msgstr "" @@ -1752,7 +1752,7 @@ msgstr "" msgid "Max Priority Fee per Gas" msgstr "" -#: lib/block_scout_web/views/transaction_view.ex:325 +#: lib/block_scout_web/views/transaction_view.ex:327 #, elixir-autogen, elixir-format msgid "Max of" msgstr "" @@ -2064,8 +2064,8 @@ msgid "Parent Hash" msgstr "" #: lib/block_scout_web/templates/layout/_topnav.html.eex:63 -#: lib/block_scout_web/views/transaction_view.ex:368 -#: lib/block_scout_web/views/transaction_view.ex:407 +#: lib/block_scout_web/views/transaction_view.ex:370 +#: lib/block_scout_web/views/transaction_view.ex:409 #, elixir-autogen, elixir-format msgid "Pending" msgstr "" @@ -2082,7 +2082,7 @@ msgid "Play" msgstr "" #: lib/block_scout_web/templates/layout/_account_menu_item.html.eex:22 -#: lib/block_scout_web/templates/layout/app.html.eex:97 +#: lib/block_scout_web/templates/layout/app.html.eex:100 #, elixir-autogen, elixir-format msgid "Please confirm your email address to use the My Account feature." msgstr "" @@ -2201,7 +2201,7 @@ msgstr "" #: lib/block_scout_web/templates/transaction/_tabs.html.eex:24 #: lib/block_scout_web/templates/transaction_raw_trace/_card_body.html.eex:1 -#: lib/block_scout_web/views/transaction_view.ex:535 +#: lib/block_scout_web/views/transaction_view.ex:537 #, elixir-autogen, elixir-format msgid "Raw Trace" msgstr "" @@ -2262,7 +2262,7 @@ msgstr "" msgid "Request to edit a public tag/label" msgstr "" -#: lib/block_scout_web/templates/layout/app.html.eex:97 +#: lib/block_scout_web/templates/layout/app.html.eex:100 #, elixir-autogen, elixir-format msgid "Resend verification email" msgstr "" @@ -2514,7 +2514,7 @@ msgstr "" #: lib/block_scout_web/templates/transaction/_tabs.html.eex:29 #: lib/block_scout_web/templates/transaction_state/index.html.eex:6 -#: lib/block_scout_web/views/transaction_view.ex:536 +#: lib/block_scout_web/views/transaction_view.ex:538 #, elixir-autogen, elixir-format msgid "State changes" msgstr "" @@ -2540,7 +2540,7 @@ msgid "Submit an Issue" msgstr "" #: lib/block_scout_web/templates/transaction/_emission_reward_tile.html.eex:8 -#: lib/block_scout_web/views/transaction_view.ex:370 +#: lib/block_scout_web/views/transaction_view.ex:372 #, elixir-autogen, elixir-format msgid "Success" msgstr "" @@ -2849,13 +2849,13 @@ msgid "Token" msgstr "" #: lib/block_scout_web/templates/common_components/_token_transfer_type_display_name.html.eex:3 -#: lib/block_scout_web/views/transaction_view.ex:469 +#: lib/block_scout_web/views/transaction_view.ex:471 #, elixir-autogen, elixir-format msgid "Token Burning" msgstr "" #: lib/block_scout_web/templates/common_components/_token_transfer_type_display_name.html.eex:7 -#: lib/block_scout_web/views/transaction_view.ex:470 +#: lib/block_scout_web/views/transaction_view.ex:472 #, elixir-autogen, elixir-format msgid "Token Creation" msgstr "" @@ -2883,14 +2883,14 @@ msgid "Token ID" msgstr "" #: lib/block_scout_web/templates/common_components/_token_transfer_type_display_name.html.eex:5 -#: lib/block_scout_web/views/transaction_view.ex:468 +#: lib/block_scout_web/views/transaction_view.ex:470 #, elixir-autogen, elixir-format msgid "Token Minting" msgstr "" #: lib/block_scout_web/templates/common_components/_token_transfer_type_display_name.html.eex:9 #: lib/block_scout_web/templates/common_components/_token_transfer_type_display_name.html.eex:11 -#: lib/block_scout_web/views/transaction_view.ex:471 +#: lib/block_scout_web/views/transaction_view.ex:473 #, elixir-autogen, elixir-format msgid "Token Transfer" msgstr "" @@ -2906,7 +2906,7 @@ msgstr "" #: lib/block_scout_web/views/address_view.ex:378 #: lib/block_scout_web/views/tokens/instance/overview_view.ex:114 #: lib/block_scout_web/views/tokens/overview_view.ex:40 -#: lib/block_scout_web/views/transaction_view.ex:532 +#: lib/block_scout_web/views/transaction_view.ex:534 #, elixir-autogen, elixir-format msgid "Token Transfers" msgstr "" @@ -2962,7 +2962,7 @@ msgstr "" msgid "Topic" msgstr "" -#: lib/block_scout_web/templates/address_logs/_logs.html.eex:71 +#: lib/block_scout_web/templates/address_logs/_logs.html.eex:68 #: lib/block_scout_web/templates/transaction_log/_logs.html.eex:91 #, elixir-autogen, elixir-format msgid "Topics" @@ -3022,7 +3022,7 @@ msgstr "" #: lib/block_scout_web/templates/account/tag_transaction/form.html.eex:11 #: lib/block_scout_web/templates/account/tag_transaction/index.html.eex:23 #: lib/block_scout_web/templates/address_logs/_logs.html.eex:19 -#: lib/block_scout_web/views/transaction_view.ex:481 +#: lib/block_scout_web/views/transaction_view.ex:483 #, elixir-autogen, elixir-format msgid "Transaction" msgstr "" @@ -3177,7 +3177,7 @@ msgstr "" msgid "Uncles" msgstr "" -#: lib/block_scout_web/views/transaction_view.ex:361 +#: lib/block_scout_web/views/transaction_view.ex:363 #, elixir-autogen, elixir-format msgid "Unconfirmed" msgstr "" @@ -3444,7 +3444,7 @@ msgstr "" msgid "We recommend using flattened code. This is necessary if your code utilizes a library or inherits dependencies. Use the" msgstr "" -#: lib/block_scout_web/views/wei_helper.ex:76 +#: lib/block_scout_web/views/wei_helper.ex:80 #, elixir-autogen, elixir-format msgid "Wei" msgstr "" @@ -3541,16 +3541,6 @@ msgstr "" msgid "balance of the address" msgstr "" -#: lib/block_scout_web/templates/transaction/overview.html.eex:437 -#, elixir-autogen, elixir-format -msgid "burned for this transaction. Equals Block Base Fee per Gas * Gas Used." -msgstr "" - -#: lib/block_scout_web/templates/block/overview.html.eex:215 -#, elixir-autogen, elixir-format -msgid "burned from transactions included in the block (Base fee (per unit of gas) * Gas Used)." -msgstr "" - #: lib/block_scout_web/templates/address_contract/index.html.eex:28 #, elixir-autogen, elixir-format msgid "button" @@ -3581,7 +3571,7 @@ msgstr "" msgid "fallback" msgstr "" -#: lib/block_scout_web/views/address_contract_view.ex:28 +#: lib/block_scout_web/views/address_contract_view.ex:30 #, elixir-autogen, elixir-format msgid "false" msgstr "" @@ -3608,7 +3598,7 @@ msgstr "" msgid "of" msgstr "" -#: lib/block_scout_web/templates/layout/app.html.eex:97 +#: lib/block_scout_web/templates/layout/app.html.eex:100 #, elixir-autogen, elixir-format msgid "on sign up. Didn’t receive?" msgstr "" @@ -3637,7 +3627,7 @@ msgstr "" msgid "string" msgstr "" -#: lib/block_scout_web/views/address_contract_view.ex:27 +#: lib/block_scout_web/views/address_contract_view.ex:29 #, elixir-autogen, elixir-format msgid "true" msgstr "" @@ -3682,3 +3672,13 @@ msgstr "" #, elixir-autogen, elixir-format msgid "Yul" msgstr "" + +#: lib/block_scout_web/templates/transaction/overview.html.eex:437 +#, elixir-autogen, elixir-format, fuzzy +msgid "burnt for this transaction. Equals Block Base Fee per Gas * Gas Used." +msgstr "" + +#: lib/block_scout_web/templates/block/overview.html.eex:215 +#, elixir-autogen, elixir-format, fuzzy +msgid "burnt from transactions included in the block (Base fee (per unit of gas) * Gas Used)." +msgstr "" diff --git a/apps/block_scout_web/test/block_scout_web/channels/websocket_v2_test.exs b/apps/block_scout_web/test/block_scout_web/channels/websocket_v2_test.exs index 918b636cb3e8..6d7aad632f90 100644 --- a/apps/block_scout_web/test/block_scout_web/channels/websocket_v2_test.exs +++ b/apps/block_scout_web/test/block_scout_web/channels/websocket_v2_test.exs @@ -6,7 +6,15 @@ defmodule BlockScoutWeb.WebsocketV2Test do alias Explorer.Chain.{Address, Import, Token, TokenTransfer, Transaction} alias Explorer.Repo + @first_topic_hex_string "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef" + @second_topic_hex_string "0x000000000000000000000000e8ddc5c7a2d2f0d7a9798459c0104fdf5e987aca" + @third_topic_hex_string "0x000000000000000000000000515c09c5bba1ed566b02a5b0599ec5d5d0aee73d" + describe "websocket v2" do + {:ok, first_topic} = Explorer.Chain.Hash.Full.cast(@first_topic_hex_string) + {:ok, second_topic} = Explorer.Chain.Hash.Full.cast(@second_topic_hex_string) + {:ok, third_topic} = Explorer.Chain.Hash.Full.cast(@third_topic_hex_string) + @import_data %{ blocks: %{ params: [ @@ -34,37 +42,34 @@ defmodule BlockScoutWeb.WebsocketV2Test do block_hash: "0xf6b4b8c88df3ebd252ec476328334dc026cf66606a84fb769b3d3cbccc8471bd", address_hash: "0x8bf38d4764929064f2d4d3a56520a76ab3df415b", data: "0x0000000000000000000000000000000000000000000000000de0b6b3a7640000", - first_topic: "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", - second_topic: "0x000000000000000000000000e8ddc5c7a2d2f0d7a9798459c0104fdf5e987aca", - third_topic: "0x000000000000000000000000515c09c5bba1ed566b02a5b0599ec5d5d0aee73d", + first_topic: first_topic, + second_topic: second_topic, + third_topic: third_topic, fourth_topic: nil, index: 0, - transaction_hash: "0x53bd884872de3e488692881baeec262e7b95234d3965248c39fe992fffd433e5", - type: "mined" + transaction_hash: "0x53bd884872de3e488692881baeec262e7b95234d3965248c39fe992fffd433e5" }, %{ block_hash: "0xf6b4b8c88df3ebd252ec476328334dc026cf66606a84fb769b3d3cbccc8471bd", address_hash: "0x8bf38d4764929064f2d4d3a56520a76ab3df415b", data: "0x0000000000000000000000000000000000000000000000000de0b6b3a7640000", - first_topic: "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", - second_topic: "0x000000000000000000000000e8ddc5c7a2d2f0d7a9798459c0104fdf5e987aca", - third_topic: "0x000000000000000000000000515c09c5bba1ed566b02a5b0599ec5d5d0aee73d", + first_topic: first_topic, + second_topic: second_topic, + third_topic: third_topic, fourth_topic: nil, index: 1, - transaction_hash: "0x53bd884872de3e488692881baeec262e7b95234d3965248c39fe992fffd433e5", - type: "mined" + transaction_hash: "0x53bd884872de3e488692881baeec262e7b95234d3965248c39fe992fffd433e5" }, %{ block_hash: "0xf6b4b8c88df3ebd252ec476328334dc026cf66606a84fb769b3d3cbccc8471bd", address_hash: "0x8bf38d4764929064f2d4d3a56520a76ab3df415b", data: "0x0000000000000000000000000000000000000000000000000de0b6b3a7640000", - first_topic: "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", - second_topic: "0x000000000000000000000000e8ddc5c7a2d2f0d7a9798459c0104fdf5e987aca", - third_topic: "0x000000000000000000000000515c09c5bba1ed566b02a5b0599ec5d5d0aee73d", + first_topic: first_topic, + second_topic: second_topic, + third_topic: third_topic, fourth_topic: nil, index: 2, - transaction_hash: "0x53bd884872de3e488692881baeec262e7b95234d3965248c39fe992fffd433e5", - type: "mined" + transaction_hash: "0x53bd884872de3e488692881baeec262e7b95234d3965248c39fe992fffd433e5" } ], timeout: 5 @@ -74,6 +79,7 @@ defmodule BlockScoutWeb.WebsocketV2Test do %{ block_hash: "0xf6b4b8c88df3ebd252ec476328334dc026cf66606a84fb769b3d3cbccc8471bd", block_number: 37, + block_timestamp: Timex.parse!("2017-12-15T21:06:30.000000Z", "{ISO:Extended:Z}"), cumulative_gas_used: 50450, from_address_hash: "0xe8ddc5c7a2d2f0d7a9798459c0104fdf5e987aca", gas: 4_700_000, @@ -96,6 +102,7 @@ defmodule BlockScoutWeb.WebsocketV2Test do %{ block_hash: "0xf6b4b8c88df3ebd252ec476328334dc026cf66606a84fb769b3d3cbccc8471bd", block_number: 37, + block_timestamp: Timex.parse!("2017-12-15T21:06:30.000000Z", "{ISO:Extended:Z}"), cumulative_gas_used: 50450, from_address_hash: "0xe8ddc5c7a2d2f0d7a9798459c0104fdf5e987aca", gas: 4_700_000, diff --git a/apps/block_scout_web/test/block_scout_web/controllers/account/api/v1/user_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/account/api/v1/user_controller_test.exs index f32b5727e727..cb8359f003f5 100644 --- a/apps/block_scout_web/test/block_scout_web/controllers/account/api/v1/user_controller_test.exs +++ b/apps/block_scout_web/test/block_scout_web/controllers/account/api/v1/user_controller_test.exs @@ -156,7 +156,8 @@ defmodule BlockScoutWeb.Account.Api.V1.UserControllerTest do "name" => nil, "private_tags" => [], "public_tags" => [], - "watchlist_names" => [] + "watchlist_names" => [], + "ens_domain_name" => nil } }} end) @@ -207,7 +208,8 @@ defmodule BlockScoutWeb.Account.Api.V1.UserControllerTest do "name" => nil, "private_tags" => [], "public_tags" => [], - "watchlist_names" => [] + "watchlist_names" => [], + "ens_domain_name" => nil } }} end) diff --git a/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/address_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/address_controller_test.exs index ae0b2f3b944e..f872c8928fd9 100644 --- a/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/address_controller_test.exs +++ b/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/address_controller_test.exs @@ -1246,7 +1246,7 @@ defmodule BlockScoutWeb.API.RPC.AddressControllerTest do for block <- Enum.concat([blocks1, blocks2, blocks3]) do 2 - |> insert_list(:transaction, from_address: address) + |> insert_list(:transaction, from_address: address, block_timestamp: block.timestamp) |> with_block(block) end @@ -1294,7 +1294,7 @@ defmodule BlockScoutWeb.API.RPC.AddressControllerTest do for block <- Enum.concat([blocks1, blocks2, blocks3]) do 2 - |> insert_list(:transaction, from_address: address) + |> insert_list(:transaction, from_address: address, block_timestamp: block.timestamp) |> with_block(block) end @@ -1342,7 +1342,7 @@ defmodule BlockScoutWeb.API.RPC.AddressControllerTest do for block <- Enum.concat([blocks1, blocks2, blocks3]) do 2 - |> insert_list(:transaction, from_address: address) + |> insert_list(:transaction, from_address: address, block_timestamp: block.timestamp) |> with_block(block) end diff --git a/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/eth_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/eth_controller_test.exs index f441a173b1e2..b77a23a39a30 100644 --- a/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/eth_controller_test.exs +++ b/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/eth_controller_test.exs @@ -5,6 +5,12 @@ defmodule BlockScoutWeb.API.RPC.EthControllerTest do alias Explorer.Repo alias Indexer.Fetcher.CoinBalanceOnDemand + @first_topic_hex_string_1 "0x7fcf532c15f0a6db0bd6d0e038bea71d30d808c7d98cb3bf7268a95bf5081b65" + @first_topic_hex_string_2 "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef" + + @second_topic_hex_string_1 "0x00000000000000000000000098a9dc37d3650b5b30d6c12789b3881ee0b70c16" + @second_topic_hex_string_2 "0x000000000000000000000000e2680fd7cdbb04e9087a647ad4d023ef6c8fb4e2" + setup do mocked_json_rpc_named_arguments = [ transport: EthereumJSONRPC.Mox, @@ -27,6 +33,11 @@ defmodule BlockScoutWeb.API.RPC.EthControllerTest do defp params(api_params, params), do: Map.put(api_params, "params", params) + defp topic(topic_hex_string) do + {:ok, topic} = Explorer.Chain.Hash.Full.cast(topic_hex_string) + topic + end + describe "eth_get_logs" do setup do %{ @@ -76,7 +87,14 @@ defmodule BlockScoutWeb.API.RPC.EthControllerTest do block = insert(:block, number: 0) transaction = insert(:transaction, from_address: address) |> with_block(block) - insert(:log, block: block, address: address, transaction: transaction, data: "0x010101") + + insert(:log, + block: block, + block_number: block.number, + address: address, + transaction: transaction, + data: "0x010101" + ) params = params(api_params, [%{"address" => to_string(address.hash)}]) @@ -94,9 +112,17 @@ defmodule BlockScoutWeb.API.RPC.EthControllerTest do block = insert(:block, number: 0) transaction = insert(:transaction, from_address: address) |> with_block(block) - insert(:log, block: block, address: address, transaction: transaction, data: "0x010101", first_topic: "0x01") - params = params(api_params, [%{"address" => to_string(address.hash), "topics" => ["0x01"]}]) + insert(:log, + block: block, + block_number: block.number, + address: address, + transaction: transaction, + data: "0x010101", + first_topic: topic(@first_topic_hex_string_1) + ) + + params = params(api_params, [%{"address" => to_string(address.hash), "topics" => [@first_topic_hex_string_1]}]) assert response = conn @@ -112,10 +138,29 @@ defmodule BlockScoutWeb.API.RPC.EthControllerTest do block = insert(:block, number: 0) transaction = insert(:transaction, from_address: address) |> with_block(block) - insert(:log, address: address, block: block, transaction: transaction, data: "0x010101", first_topic: "0x01") - insert(:log, address: address, block: block, transaction: transaction, data: "0x020202", first_topic: "0x00") - params = params(api_params, [%{"address" => to_string(address.hash), "topics" => [["0x01", "0x00"]]}]) + insert(:log, + address: address, + block: block, + block_number: block.number, + transaction: transaction, + data: "0x010101", + first_topic: topic(@first_topic_hex_string_1) + ) + + insert(:log, + address: address, + block: block, + block_number: block.number, + transaction: transaction, + data: "0x020202", + first_topic: topic(@first_topic_hex_string_2) + ) + + params = + params(api_params, [ + %{"address" => to_string(address.hash), "topics" => [[@first_topic_hex_string_1, @first_topic_hex_string_2]]} + ]) assert response = conn @@ -135,9 +180,16 @@ defmodule BlockScoutWeb.API.RPC.EthControllerTest do |> with_block(block) inserted_records = - insert_list(2000, :log, block: block, address: contract_address, transaction: transaction, first_topic: "0x01") + insert_list(2000, :log, + block: block, + block_number: block.number, + address: contract_address, + transaction: transaction, + first_topic: topic(@first_topic_hex_string_1) + ) - params = params(api_params, [%{"address" => to_string(contract_address), "topics" => [["0x01"]]}]) + params = + params(api_params, [%{"address" => to_string(contract_address), "topics" => [[@first_topic_hex_string_1]]}]) assert response = conn @@ -150,13 +202,16 @@ defmodule BlockScoutWeb.API.RPC.EthControllerTest do next_page_params = %{ "blockNumber" => Integer.to_string(transaction.block_number, 16), - "transactionIndex" => transaction.index, "logIndex" => Integer.to_string(last_log_index, 16) } new_params = params(api_params, [ - %{"paging_options" => next_page_params, "address" => to_string(contract_address), "topics" => [["0x01"]]} + %{ + "paging_options" => next_page_params, + "address" => to_string(contract_address), + "topics" => [[@first_topic_hex_string_1]] + } ]) assert new_response = @@ -191,14 +246,24 @@ defmodule BlockScoutWeb.API.RPC.EthControllerTest do address: address, transaction: transaction, data: "0x010101", - first_topic: "0x01", - second_topic: "0x02", - block: block + first_topic: topic(@first_topic_hex_string_1), + second_topic: topic(@second_topic_hex_string_1), + block: block, + block_number: block.number ) - insert(:log, block: block, address: address, transaction: transaction, data: "0x020202", first_topic: "0x01") + insert(:log, + block: block, + address: address, + transaction: transaction, + data: "0x020202", + first_topic: topic(@first_topic_hex_string_1) + ) - params = params(api_params, [%{"address" => to_string(address.hash), "topics" => ["0x01", "0x02"]}]) + params = + params(api_params, [ + %{"address" => to_string(address.hash), "topics" => [@first_topic_hex_string_1, @second_topic_hex_string_1]} + ]) assert response = conn @@ -220,21 +285,29 @@ defmodule BlockScoutWeb.API.RPC.EthControllerTest do address: address, transaction: transaction, data: "0x010101", - first_topic: "0x01", - second_topic: "0x02", - block: block + first_topic: topic(@first_topic_hex_string_1), + second_topic: topic(@second_topic_hex_string_1), + block: block, + block_number: block.number ) insert(:log, address: address, transaction: transaction, data: "0x020202", - first_topic: "0x01", - second_topic: "0x03", - block: block + first_topic: topic(@first_topic_hex_string_1), + second_topic: topic(@second_topic_hex_string_2), + block: block, + block_number: block.number ) - params = params(api_params, [%{"address" => to_string(address.hash), "topics" => ["0x01", ["0x02", "0x03"]]}]) + params = + params(api_params, [ + %{ + "address" => to_string(address.hash), + "topics" => [@first_topic_hex_string_1, [@second_topic_hex_string_1, @second_topic_hex_string_2]] + } + ]) assert response = conn @@ -258,13 +331,13 @@ defmodule BlockScoutWeb.API.RPC.EthControllerTest do transaction3 = insert(:transaction, from_address: address) |> with_block(block3) transaction4 = insert(:transaction, from_address: address) |> with_block(block4) - insert(:log, address: address, transaction: transaction1, data: "0x010101") + insert(:log, address: address, transaction: transaction1, data: "0x010101", block_number: block1.number) - insert(:log, address: address, transaction: transaction2, data: "0x020202") + insert(:log, address: address, transaction: transaction2, data: "0x020202", block_number: block2.number) - insert(:log, address: address, transaction: transaction3, data: "0x030303") + insert(:log, address: address, transaction: transaction3, data: "0x030303", block_number: block3.number) - insert(:log, address: address, transaction: transaction4, data: "0x040404") + insert(:log, address: address, transaction: transaction4, data: "0x040404", block_number: block4.number) params = params(api_params, [%{"address" => to_string(address.hash), "fromBlock" => 1, "toBlock" => 2}]) @@ -288,11 +361,11 @@ defmodule BlockScoutWeb.API.RPC.EthControllerTest do transaction2 = insert(:transaction, from_address: address) |> with_block(block2) transaction3 = insert(:transaction, from_address: address) |> with_block(block3) - insert(:log, address: address, transaction: transaction1, data: "0x010101") + insert(:log, address: address, transaction: transaction1, data: "0x010101", block_number: block1.number) - insert(:log, address: address, transaction: transaction2, data: "0x020202") + insert(:log, address: address, transaction: transaction2, data: "0x020202", block_number: block2.number) - insert(:log, address: address, transaction: transaction3, data: "0x030303") + insert(:log, address: address, transaction: transaction3, data: "0x030303", block_number: block3.number) params = params(api_params, [%{"address" => to_string(address.hash), "blockHash" => to_string(block2.hash)}]) @@ -316,11 +389,11 @@ defmodule BlockScoutWeb.API.RPC.EthControllerTest do transaction2 = insert(:transaction, from_address: address) |> with_block(block2) transaction3 = insert(:transaction, from_address: address) |> with_block(block3) - insert(:log, address: address, transaction: transaction1, data: "0x010101") + insert(:log, address: address, transaction: transaction1, data: "0x010101", block_number: block1.number) - insert(:log, address: address, transaction: transaction2, data: "0x020202") + insert(:log, address: address, transaction: transaction2, data: "0x020202", block_number: block2.number) - insert(:log, address: address, transaction: transaction3, data: "0x030303") + insert(:log, address: address, transaction: transaction3, data: "0x030303", block_number: block3.number) params = params(api_params, [%{"address" => to_string(address.hash), "fromBlock" => "earliest", "toBlock" => "earliest"}]) @@ -345,11 +418,29 @@ defmodule BlockScoutWeb.API.RPC.EthControllerTest do transaction2 = insert(:transaction, from_address: address) |> with_block(block2) transaction3 = insert(:transaction, from_address: address) |> with_block(block3) - insert(:log, block: block1, address: address, transaction: transaction1, data: "0x010101") + insert(:log, + block: block1, + block_number: block1.number, + address: address, + transaction: transaction1, + data: "0x010101" + ) - insert(:log, block: block2, address: address, transaction: transaction2, data: "0x020202") + insert(:log, + block: block2, + block_number: block2.number, + address: address, + transaction: transaction2, + data: "0x020202" + ) - insert(:log, block: block3, address: address, transaction: transaction3, data: "0x030303") + insert(:log, + block: block3, + block_number: block3.number, + address: address, + transaction: transaction3, + data: "0x030303" + ) changeset = Ecto.Changeset.change(block3, %{consensus: false}) diff --git a/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/logs_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/logs_controller_test.exs index ec4337eecd84..2691f1e7e825 100644 --- a/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/logs_controller_test.exs +++ b/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/logs_controller_test.exs @@ -4,6 +4,22 @@ defmodule BlockScoutWeb.API.RPC.LogsControllerTest do alias BlockScoutWeb.API.RPC.LogsController alias Explorer.Chain.{Log, Transaction} + @first_topic_hex_string_1 "0x7fcf532c15f0a6db0bd6d0e038bea71d30d808c7d98cb3bf7268a95bf5081b65" + @first_topic_hex_string_2 "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef" + + @second_topic_hex_string_1 "0x00000000000000000000000098a9dc37d3650b5b30d6c12789b3881ee0b70c16" + @second_topic_hex_string_2 "0x000000000000000000000000e2680fd7cdbb04e9087a647ad4d023ef6c8fb4e2" + + @third_topic_hex_string_1 "0x0000000000000000000000005079fc00f00f30000e0c8c083801cfde000008b6" + + @fourth_topic_hex_string_1 "0x8c9b7729443a4444242342b2ca385a239a5c1d76a88473e1cd2ab0c70dd1b9c7" + @fourth_topic_hex_string_2 "0x232b688786cc0d24a11e07563c1bfa129537cec9385dc5b1fb8f86462977239b" + + defp topic(topic_hex_string) do + {:ok, topic} = Explorer.Chain.Hash.Full.cast(topic_hex_string) + topic + end + describe "getLogs" do test "without fromBlock, toBlock, address, and topic{x}", %{conn: conn} do params = %{ @@ -280,7 +296,7 @@ defmodule BlockScoutWeb.API.RPC.LogsControllerTest do |> insert(to_address: contract_address) |> with_block() - log = insert(:log, address: contract_address, transaction: transaction) + log = insert(:log, address: contract_address, transaction: transaction, block_number: transaction.block_number) params = %{ "module" => "logs", @@ -334,8 +350,17 @@ defmodule BlockScoutWeb.API.RPC.LogsControllerTest do |> insert(to_address: contract_address) |> with_block(second_block) - insert(:log, address: contract_address, transaction: transaction_block1) - insert(:log, address: contract_address, transaction: transaction_block2) + insert(:log, + address: contract_address, + transaction: transaction_block1, + block_number: transaction_block1.block_number + ) + + insert(:log, + address: contract_address, + transaction: transaction_block2, + block_number: transaction_block2.block_number + ) params = %{ "module" => "logs", @@ -378,8 +403,17 @@ defmodule BlockScoutWeb.API.RPC.LogsControllerTest do |> insert(to_address: contract_address) |> with_block(second_block) - insert(:log, address: contract_address, transaction: transaction_block1) - insert(:log, address: contract_address, transaction: transaction_block2) + insert(:log, + address: contract_address, + transaction: transaction_block1, + block_number: transaction_block1.block_number + ) + + insert(:log, + address: contract_address, + transaction: transaction_block2, + block_number: transaction_block2.block_number + ) params = %{ "module" => "logs", @@ -416,13 +450,13 @@ defmodule BlockScoutWeb.API.RPC.LogsControllerTest do log1_details = [ address: contract_address, transaction: transaction, - first_topic: "some topic" + first_topic: topic(@first_topic_hex_string_1) ] log2_details = [ address: contract_address, transaction: transaction, - first_topic: "some other topic" + first_topic: topic(@first_topic_hex_string_2) ] log1 = insert(:log, log1_details) @@ -474,15 +508,15 @@ defmodule BlockScoutWeb.API.RPC.LogsControllerTest do log1_details = [ address: contract_address, transaction: transaction, - first_topic: "some topic", - second_topic: "some second topic" + first_topic: topic(@first_topic_hex_string_1), + second_topic: topic(@second_topic_hex_string_1) ] log2_details = [ address: contract_address, transaction: transaction, - first_topic: "some other topic", - second_topic: "some other second topic" + first_topic: topic(@first_topic_hex_string_2), + second_topic: topic(@second_topic_hex_string_2) ] log1 = insert(:log, log1_details) @@ -523,15 +557,15 @@ defmodule BlockScoutWeb.API.RPC.LogsControllerTest do log1_details = [ address: contract_address, transaction: transaction, - first_topic: "some topic", - second_topic: "some second topic" + first_topic: topic(@first_topic_hex_string_1), + second_topic: topic(@second_topic_hex_string_1) ] log2_details = [ address: contract_address, transaction: transaction, - first_topic: "some other topic", - second_topic: "some other second topic" + first_topic: topic(@first_topic_hex_string_2), + second_topic: topic(@second_topic_hex_string_2) ] log1 = insert(:log, log1_details) @@ -571,19 +605,19 @@ defmodule BlockScoutWeb.API.RPC.LogsControllerTest do log1_details = [ address: contract_address, transaction: transaction, - first_topic: "some topic", - second_topic: "some second topic", - third_topic: "some third topic", - fourth_topic: "some fourth topic" + first_topic: topic(@first_topic_hex_string_1), + second_topic: topic(@second_topic_hex_string_1), + third_topic: topic(@third_topic_hex_string_1), + fourth_topic: topic(@fourth_topic_hex_string_1) ] log2_details = [ address: contract_address, transaction: transaction, - first_topic: "some topic", - second_topic: "some second topic", - third_topic: "some third topic", - fourth_topic: "some other fourth topic" + first_topic: topic(@first_topic_hex_string_1), + second_topic: topic(@second_topic_hex_string_1), + third_topic: topic(@third_topic_hex_string_1), + fourth_topic: topic(@fourth_topic_hex_string_2) ] log1 = insert(:log, log1_details) @@ -773,7 +807,12 @@ defmodule BlockScoutWeb.API.RPC.LogsControllerTest do third_topic: third_topic, fourth_topic: fourth_topic }) do - [first_topic, second_topic, third_topic, fourth_topic] + [ + first_topic && Explorer.Chain.Hash.to_string(first_topic), + second_topic && Explorer.Chain.Hash.to_string(second_topic), + third_topic && Explorer.Chain.Hash.to_string(third_topic), + fourth_topic && Explorer.Chain.Hash.to_string(fourth_topic) + ] end defp integer_to_hex(integer), do: "0x" <> String.downcase(Integer.to_string(integer, 16)) diff --git a/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/transaction_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/transaction_controller_test.exs index 4a828099ce6e..8e30a3e68046 100644 --- a/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/transaction_controller_test.exs +++ b/apps/block_scout_web/test/block_scout_web/controllers/api/rpc/transaction_controller_test.exs @@ -5,8 +5,16 @@ defmodule BlockScoutWeb.API.RPC.TransactionControllerTest do @moduletag capture_log: true + @first_topic_hex_string_1 "0x7fcf532c15f0a6db0bd6d0e038bea71d30d808c7d98cb3bf7268a95bf5081b65" + @second_topic_hex_string_1 "0x00000000000000000000000098a9dc37d3650b5b30d6c12789b3881ee0b70c16" + setup :verify_on_exit! + defp topic(topic_hex_string) do + {:ok, topic} = Explorer.Chain.Hash.Full.cast(topic_hex_string) + topic + end + describe "gettxreceiptstatus" do test "with missing txhash", %{conn: conn} do params = %{ @@ -414,8 +422,8 @@ defmodule BlockScoutWeb.API.RPC.TransactionControllerTest do insert(:log, address: address, transaction: transaction, - first_topic: "first topic", - second_topic: "second topic", + first_topic: topic(@first_topic_hex_string_1), + second_topic: topic(@second_topic_hex_string_1), block: block, block_number: block.number ) @@ -491,8 +499,8 @@ defmodule BlockScoutWeb.API.RPC.TransactionControllerTest do insert(:log, address: address, transaction: transaction, - first_topic: "first topic", - second_topic: "second topic", + first_topic: topic(@first_topic_hex_string_1), + second_topic: topic(@second_topic_hex_string_1), block: block, block_number: block.number ) @@ -520,7 +528,7 @@ defmodule BlockScoutWeb.API.RPC.TransactionControllerTest do %{ "address" => "#{address.hash}", "data" => "#{log.data}", - "topics" => ["first topic", "second topic", nil, nil], + "topics" => [@first_topic_hex_string_1, @second_topic_hex_string_1, nil, nil], "index" => "#{log.index}" } ], diff --git a/apps/block_scout_web/test/block_scout_web/controllers/api/v2/address_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/api/v2/address_controller_test.exs index 967c9cee5f1a..34829785f55e 100644 --- a/apps/block_scout_web/test/block_scout_web/controllers/api/v2/address_controller_test.exs +++ b/apps/block_scout_web/test/block_scout_web/controllers/api/v2/address_controller_test.exs @@ -16,6 +16,7 @@ defmodule BlockScoutWeb.API.V2.AddressControllerTest do Token.Instance, TokenTransfer, Transaction, + Wei, Withdrawal } @@ -25,10 +26,18 @@ defmodule BlockScoutWeb.API.V2.AddressControllerTest do import Explorer.Chain, only: [hash_to_lower_case_string: 1] import Mox + @first_topic_hex_string_1 "0x7fcf532c15f0a6db0bd6d0e038bea71d30d808c7d98cb3bf7268a95bf5081b65" + @instances_amount_in_collection 9 + setup :set_mox_global setup :verify_on_exit! + defp topic(topic_hex_string) do + {:ok, topic} = Explorer.Chain.Hash.Full.cast(topic_hex_string) + topic + end + describe "/addresses/{address_hash}" do test "get 404 on non existing address", %{conn: conn} do address = build(:address) @@ -75,7 +84,8 @@ defmodule BlockScoutWeb.API.V2.AddressControllerTest do "has_tokens" => false, "has_token_transfers" => false, "watchlist_address_id" => nil, - "has_beacon_chain_withdrawals" => false + "has_beacon_chain_withdrawals" => false, + "ens_domain_name" => nil } request = get(conn, "/api/v2/addresses/#{Address.checksum(address.hash)}") @@ -422,6 +432,211 @@ defmodule BlockScoutWeb.API.V2.AddressControllerTest do check_paginated_response(response_2nd_page, response, txs_from ++ [Enum.at(txs_to, 0)]) end + + test "ignores wrong ordering params", %{conn: conn} do + address = insert(:address) + + txs = insert_list(51, :transaction, from_address: address) |> with_block() + + request = get(conn, "/api/v2/addresses/#{address.hash}/transactions", %{"sort" => "foo", "order" => "bar"}) + assert response = json_response(request, 200) + + request_2nd_page = + get( + conn, + "/api/v2/addresses/#{address.hash}/transactions", + %{"sort" => "foo", "order" => "bar"} |> Map.merge(response["next_page_params"]) + ) + + assert response_2nd_page = json_response(request_2nd_page, 200) + + check_paginated_response(response, response_2nd_page, txs) + end + + test "backward compatible with legacy paging params", %{conn: conn} do + address = insert(:address) + block = insert(:block) + + txs = insert_list(51, :transaction, from_address: address) |> with_block(block) + + [_, tx_before_last | _] = txs + + request = get(conn, "/api/v2/addresses/#{address.hash}/transactions") + assert response = json_response(request, 200) + + request_2nd_page = + get( + conn, + "/api/v2/addresses/#{address.hash}/transactions", + %{"block_number" => to_string(block.number), "index" => to_string(tx_before_last.index)} + ) + + assert response_2nd_page = json_response(request_2nd_page, 200) + + check_paginated_response(response, response_2nd_page, txs) + end + + test "backward compatible with legacy paging params for pending transactions", %{conn: conn} do + address = insert(:address) + + txs = insert_list(51, :transaction, from_address: address) + + [_, tx_before_last | _] = txs + + request = get(conn, "/api/v2/addresses/#{address.hash}/transactions") + assert response = json_response(request, 200) + + request_2nd_page_pending = + get( + conn, + "/api/v2/addresses/#{address.hash}/transactions", + %{"inserted_at" => to_string(tx_before_last.inserted_at), "hash" => to_string(tx_before_last.hash)} + ) + + assert response_2nd_page_pending = json_response(request_2nd_page_pending, 200) + + check_paginated_response(response, response_2nd_page_pending, txs) + end + + test "can order and paginate by fee ascending", %{conn: conn} do + address = insert(:address) + + txs_from = insert_list(25, :transaction, from_address: address) |> with_block() + txs_to = insert_list(26, :transaction, to_address: address) |> with_block() + + txs = + (txs_from ++ txs_to) + |> Enum.sort( + &(Decimal.compare(&1 |> Chain.fee(:wei) |> elem(1), &2 |> Chain.fee(:wei) |> elem(1)) in [:eq, :lt]) + ) + + request = get(conn, "/api/v2/addresses/#{address.hash}/transactions", %{"sort" => "fee", "order" => "asc"}) + assert response = json_response(request, 200) + + request_2nd_page = + get( + conn, + "/api/v2/addresses/#{address.hash}/transactions", + %{"sort" => "fee", "order" => "asc"} |> Map.merge(response["next_page_params"]) + ) + + assert response_2nd_page = json_response(request_2nd_page, 200) + + assert Enum.count(response["items"]) == 50 + assert response["next_page_params"] != nil + compare_item(Enum.at(txs, 0), Enum.at(response["items"], 0)) + compare_item(Enum.at(txs, 49), Enum.at(response["items"], 49)) + + assert Enum.count(response_2nd_page["items"]) == 1 + assert response_2nd_page["next_page_params"] == nil + compare_item(Enum.at(txs, 50), Enum.at(response_2nd_page["items"], 0)) + + check_paginated_response(response, response_2nd_page, txs |> Enum.reverse()) + end + + test "can order and paginate by fee descending", %{conn: conn} do + address = insert(:address) + + txs_from = insert_list(25, :transaction, from_address: address) |> with_block() + txs_to = insert_list(26, :transaction, to_address: address) |> with_block() + + txs = + (txs_from ++ txs_to) + |> Enum.sort( + &(Decimal.compare(&1 |> Chain.fee(:wei) |> elem(1), &2 |> Chain.fee(:wei) |> elem(1)) in [:eq, :gt]) + ) + + request = get(conn, "/api/v2/addresses/#{address.hash}/transactions", %{"sort" => "fee", "order" => "desc"}) + assert response = json_response(request, 200) + + request_2nd_page = + get( + conn, + "/api/v2/addresses/#{address.hash}/transactions", + %{"sort" => "fee", "order" => "desc"} |> Map.merge(response["next_page_params"]) + ) + + assert response_2nd_page = json_response(request_2nd_page, 200) + + assert Enum.count(response["items"]) == 50 + assert response["next_page_params"] != nil + compare_item(Enum.at(txs, 0), Enum.at(response["items"], 0)) + compare_item(Enum.at(txs, 49), Enum.at(response["items"], 49)) + + assert Enum.count(response_2nd_page["items"]) == 1 + assert response_2nd_page["next_page_params"] == nil + compare_item(Enum.at(txs, 50), Enum.at(response_2nd_page["items"], 0)) + + check_paginated_response(response, response_2nd_page, txs |> Enum.reverse()) + end + + test "can order and paginate by value ascending", %{conn: conn} do + address = insert(:address) + + txs_from = insert_list(25, :transaction, from_address: address) |> with_block() + txs_to = insert_list(26, :transaction, to_address: address) |> with_block() + + txs = + (txs_from ++ txs_to) + |> Enum.sort(&(Decimal.compare(Wei.to(&1.value, :wei), Wei.to(&2.value, :wei)) in [:eq, :lt])) + + request = get(conn, "/api/v2/addresses/#{address.hash}/transactions", %{"sort" => "value", "order" => "asc"}) + assert response = json_response(request, 200) + + request_2nd_page = + get( + conn, + "/api/v2/addresses/#{address.hash}/transactions", + %{"sort" => "value", "order" => "asc"} |> Map.merge(response["next_page_params"]) + ) + + assert response_2nd_page = json_response(request_2nd_page, 200) + + assert Enum.count(response["items"]) == 50 + assert response["next_page_params"] != nil + compare_item(Enum.at(txs, 0), Enum.at(response["items"], 0)) + compare_item(Enum.at(txs, 49), Enum.at(response["items"], 49)) + + assert Enum.count(response_2nd_page["items"]) == 1 + assert response_2nd_page["next_page_params"] == nil + compare_item(Enum.at(txs, 50), Enum.at(response_2nd_page["items"], 0)) + + check_paginated_response(response, response_2nd_page, txs |> Enum.reverse()) + end + + test "can order and paginate by value descending", %{conn: conn} do + address = insert(:address) + + txs_from = insert_list(25, :transaction, from_address: address) |> with_block() + txs_to = insert_list(26, :transaction, to_address: address) |> with_block() + + txs = + (txs_from ++ txs_to) + |> Enum.sort(&(Decimal.compare(Wei.to(&1.value, :wei), Wei.to(&2.value, :wei)) in [:eq, :gt])) + + request = get(conn, "/api/v2/addresses/#{address.hash}/transactions", %{"sort" => "value", "order" => "desc"}) + assert response = json_response(request, 200) + + request_2nd_page = + get( + conn, + "/api/v2/addresses/#{address.hash}/transactions", + %{"sort" => "value", "order" => "desc"} |> Map.merge(response["next_page_params"]) + ) + + assert response_2nd_page = json_response(request_2nd_page, 200) + + assert Enum.count(response["items"]) == 50 + assert response["next_page_params"] != nil + compare_item(Enum.at(txs, 0), Enum.at(response["items"], 0)) + compare_item(Enum.at(txs, 49), Enum.at(response["items"], 49)) + + assert Enum.count(response_2nd_page["items"]) == 1 + assert response_2nd_page["next_page_params"] == nil + compare_item(Enum.at(txs, 50), Enum.at(response_2nd_page["items"], 0)) + + check_paginated_response(response, response_2nd_page, txs |> Enum.reverse()) + end end describe "/addresses/{address_hash}/token-transfers" do @@ -1552,10 +1767,10 @@ defmodule BlockScoutWeb.API.V2.AddressControllerTest do block: tx.block, block_number: tx.block_number, address: address, - first_topic: "0x123456789123456789" + first_topic: topic(@first_topic_hex_string_1) ) - request = get(conn, "/api/v2/addresses/#{address.hash}/logs?topic=0x123456789123456789") + request = get(conn, "/api/v2/addresses/#{address.hash}/logs?topic=#{@first_topic_hex_string_1}") assert response = json_response(request, 200) assert Enum.count(response["items"]) == 1 assert response["next_page_params"] == nil @@ -2581,10 +2796,10 @@ defmodule BlockScoutWeb.API.V2.AddressControllerTest do token_name = token.name amount = to_string(ctb.distinct_token_instances_count || ctb.value) - assert Enum.count(json["token_instances"]) == 15 + assert Enum.count(json["token_instances"]) == @instances_amount_in_collection token_instances - |> Enum.take(15) + |> Enum.take(@instances_amount_in_collection) |> Enum.with_index() |> Enum.each(fn {instance, index} -> compare_token_instance_in_collection(instance, Enum.at(json["token_instances"], index)) @@ -2602,10 +2817,10 @@ defmodule BlockScoutWeb.API.V2.AddressControllerTest do token_name = token.name amount = to_string(amount) - assert Enum.count(json["token_instances"]) == 15 + assert Enum.count(json["token_instances"]) == @instances_amount_in_collection token_instances - |> Enum.take(15) + |> Enum.take(@instances_amount_in_collection) |> Enum.with_index() |> Enum.each(fn {instance, index} -> compare_token_instance_in_collection(instance, Enum.at(json["token_instances"], index)) diff --git a/apps/block_scout_web/test/block_scout_web/controllers/api/v2/search_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/api/v2/search_controller_test.exs index ee9ee43a0371..95584c013bff 100644 --- a/apps/block_scout_web/test/block_scout_web/controllers/api/v2/search_controller_test.exs +++ b/apps/block_scout_web/test/block_scout_web/controllers/api/v2/search_controller_test.exs @@ -202,7 +202,7 @@ defmodule BlockScoutWeb.API.V2.SearchControllerTest do end test "search transaction", %{conn: conn} do - tx = insert(:transaction) + tx = insert(:transaction, block_timestamp: nil) request = get(conn, "/api/v2/search?q=#{tx.hash}") assert response = json_response(request, 200) diff --git a/apps/block_scout_web/test/block_scout_web/controllers/api/v2/smart_contract_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/api/v2/smart_contract_controller_test.exs index ad6eabf1f113..42477f8fa099 100644 --- a/apps/block_scout_web/test/block_scout_web/controllers/api/v2/smart_contract_controller_test.exs +++ b/apps/block_scout_web/test/block_scout_web/controllers/api/v2/smart_contract_controller_test.exs @@ -2026,18 +2026,7 @@ defmodule BlockScoutWeb.API.V2.SmartContractControllerTest do target_contract = insert(:smart_contract, abi: abi) - expect(EthereumJSONRPC.Mox, :json_rpc, fn %{ - id: 0, - method: "eth_getStorageAt", - params: [ - _, - "0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc", - "latest" - ] - }, - _options -> - {:ok, "0x000000000000000000000000#{target_contract.address_hash |> to_string() |> String.replace("0x", "")}"} - end) + mock_logic_storage_pointer_request(target_contract.address_hash) expect( EthereumJSONRPC.Mox, @@ -2136,18 +2125,7 @@ defmodule BlockScoutWeb.API.V2.SmartContractControllerTest do target_contract = insert(:smart_contract, abi: abi) - expect(EthereumJSONRPC.Mox, :json_rpc, fn %{ - id: 0, - method: "eth_getStorageAt", - params: [ - _, - "0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc", - "latest" - ] - }, - _options -> - {:ok, "0x000000000000000000000000#{target_contract.address_hash |> to_string() |> String.replace("0x", "")}"} - end) + mock_logic_storage_pointer_request(target_contract.address_hash) expect( EthereumJSONRPC.Mox, @@ -2221,18 +2199,7 @@ defmodule BlockScoutWeb.API.V2.SmartContractControllerTest do target_contract = insert(:smart_contract, abi: abi) - expect(EthereumJSONRPC.Mox, :json_rpc, fn %{ - id: 0, - method: "eth_getStorageAt", - params: [ - _, - "0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc", - "latest" - ] - }, - _options -> - {:ok, "0x000000000000000000000000#{target_contract.address_hash |> to_string() |> String.replace("0x", "")}"} - end) + mock_logic_storage_pointer_request(target_contract.address_hash) expect( EthereumJSONRPC.Mox, @@ -2290,18 +2257,7 @@ defmodule BlockScoutWeb.API.V2.SmartContractControllerTest do target_contract = insert(:smart_contract, abi: abi) - expect(EthereumJSONRPC.Mox, :json_rpc, fn %{ - id: 0, - method: "eth_getStorageAt", - params: [ - _, - "0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc", - "latest" - ] - }, - _options -> - {:ok, "0x000000000000000000000000#{target_contract.address_hash |> to_string() |> String.replace("0x", "")}"} - end) + mock_logic_storage_pointer_request(target_contract.address_hash) expect( EthereumJSONRPC.Mox, @@ -2358,18 +2314,7 @@ defmodule BlockScoutWeb.API.V2.SmartContractControllerTest do target_contract = insert(:smart_contract, abi: abi) - expect(EthereumJSONRPC.Mox, :json_rpc, fn %{ - id: 0, - method: "eth_getStorageAt", - params: [ - _, - "0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc", - "latest" - ] - }, - _options -> - {:ok, "0x000000000000000000000000#{target_contract.address_hash |> to_string() |> String.replace("0x", "")}"} - end) + mock_logic_storage_pointer_request(target_contract.address_hash) expect( EthereumJSONRPC.Mox, @@ -2450,18 +2395,7 @@ defmodule BlockScoutWeb.API.V2.SmartContractControllerTest do target_contract = insert(:smart_contract, abi: abi) - expect(EthereumJSONRPC.Mox, :json_rpc, fn %{ - id: 0, - method: "eth_getStorageAt", - params: [ - _, - "0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc", - "latest" - ] - }, - _options -> - {:ok, "0x000000000000000000000000#{target_contract.address_hash |> to_string() |> String.replace("0x", "")}"} - end) + mock_logic_storage_pointer_request(target_contract.address_hash) contract = insert(:smart_contract) @@ -2513,6 +2447,107 @@ defmodule BlockScoutWeb.API.V2.SmartContractControllerTest do check_paginated_response(response, response_2nd_page, smart_contracts) end + + test "ignores wrong ordering params", %{conn: conn} do + smart_contracts = + for _ <- 0..50 do + insert(:smart_contract) + end + + ordering_params = %{"sort" => "foo", "order" => "bar"} + + request = get(conn, "/api/v2/smart-contracts", ordering_params) + assert response = json_response(request, 200) + + request_2nd_page = + get(conn, "/api/v2/smart-contracts", ordering_params |> Map.merge(response["next_page_params"])) + + assert response_2nd_page = json_response(request_2nd_page, 200) + + check_paginated_response(response, response_2nd_page, smart_contracts) + end + + test "can order by balance ascending", %{conn: conn} do + smart_contracts = + for i <- 0..50 do + address = insert(:address, fetched_coin_balance: i) + insert(:smart_contract, address_hash: address.hash, address: address) + end + |> Enum.reverse() + + ordering_params = %{"sort" => "balance", "order" => "asc"} + + request = get(conn, "/api/v2/smart-contracts", ordering_params) + assert response = json_response(request, 200) + + request_2nd_page = + get(conn, "/api/v2/smart-contracts", ordering_params |> Map.merge(response["next_page_params"])) + + assert response_2nd_page = json_response(request_2nd_page, 200) + + check_paginated_response(response, response_2nd_page, smart_contracts) + end + + test "can order by balance descending", %{conn: conn} do + smart_contracts = + for i <- 0..50 do + address = insert(:address, fetched_coin_balance: i) + insert(:smart_contract, address_hash: address.hash, address: address) + end + + ordering_params = %{"sort" => "balance", "order" => "desc"} + + request = get(conn, "/api/v2/smart-contracts", ordering_params) + assert response = json_response(request, 200) + + request_2nd_page = + get(conn, "/api/v2/smart-contracts", ordering_params |> Map.merge(response["next_page_params"])) + + assert response_2nd_page = json_response(request_2nd_page, 200) + + check_paginated_response(response, response_2nd_page, smart_contracts) + end + + test "can order by transaction count ascending", %{conn: conn} do + smart_contracts = + for i <- 0..50 do + address = insert(:address, transactions_count: i) + insert(:smart_contract, address_hash: address.hash, address: address) + end + |> Enum.reverse() + + ordering_params = %{"sort" => "txs_count", "order" => "asc"} + + request = get(conn, "/api/v2/smart-contracts", ordering_params) + assert response = json_response(request, 200) + + request_2nd_page = + get(conn, "/api/v2/smart-contracts", ordering_params |> Map.merge(response["next_page_params"])) + + assert response_2nd_page = json_response(request_2nd_page, 200) + + check_paginated_response(response, response_2nd_page, smart_contracts) + end + + test "can order by transaction count descending", %{conn: conn} do + smart_contracts = + for i <- 0..50 do + address = insert(:address, transactions_count: i) + insert(:smart_contract, address_hash: address.hash, address: address) + end + + ordering_params = %{"sort" => "txs_count", "order" => "desc"} + + request = get(conn, "/api/v2/smart-contracts", ordering_params) + assert response = json_response(request, 200) + + request_2nd_page = + get(conn, "/api/v2/smart-contracts", ordering_params |> Map.merge(response["next_page_params"])) + + assert response_2nd_page = json_response(request_2nd_page, 200) + + check_paginated_response(response, response_2nd_page, smart_contracts) + end end describe "/smart-contracts/counters" do @@ -2579,4 +2614,19 @@ defmodule BlockScoutWeb.API.V2.SmartContractControllerTest do "solidity" end end + + defp mock_logic_storage_pointer_request(address_hash) do + expect(EthereumJSONRPC.Mox, :json_rpc, fn %{ + id: 0, + method: "eth_getStorageAt", + params: [ + _, + "0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc", + "latest" + ] + }, + _options -> + {:ok, "0x000000000000000000000000#{address_hash |> to_string() |> String.replace("0x", "")}"} + end) + end end diff --git a/apps/block_scout_web/test/block_scout_web/controllers/api/v2/token_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/api/v2/token_controller_test.exs index d3506dbe5191..daefeaa8cb85 100644 --- a/apps/block_scout_web/test/block_scout_web/controllers/api/v2/token_controller_test.exs +++ b/apps/block_scout_web/test/block_scout_web/controllers/api/v2/token_controller_test.exs @@ -593,6 +593,23 @@ defmodule BlockScoutWeb.API.V2.TokenControllerTest do assert %{"items" => [], "next_page_params" => nil} = json_response(request, 200) end + test "ignores wrong ordering params", %{conn: conn} do + tokens = + for i <- 0..50 do + insert(:token, fiat_value: i) + end + + request = get(conn, "/api/v2/tokens", %{"sort" => "foo", "order" => "bar"}) + + assert response = json_response(request, 200) + + request_2nd_page = + get(conn, "/api/v2/tokens", %{"sort" => "foo", "order" => "bar"} |> Map.merge(response["next_page_params"])) + + assert response_2nd_page = json_response(request_2nd_page, 200) + check_paginated_response(response, response_2nd_page, tokens) + end + test "tokens are filtered by single type", %{conn: conn} do erc_20_tokens = for i <- 0..50 do @@ -609,14 +626,14 @@ defmodule BlockScoutWeb.API.V2.TokenControllerTest do insert(:token, type: "ERC-1155") end - check_tokens_pagination(erc_20_tokens |> Enum.reverse(), conn, %{"type" => "ERC-20"}) + check_tokens_pagination(erc_20_tokens, conn, %{"type" => "ERC-20"}) check_tokens_pagination(erc_721_tokens |> Enum.reverse(), conn, %{"type" => "ERC-721"}) check_tokens_pagination(erc_1155_tokens |> Enum.reverse(), conn, %{"type" => "ERC-1155"}) end test "tokens are filtered by multiple type", %{conn: conn} do erc_20_tokens = - for i <- 0..25 do + for i <- 11..36 do insert(:token, fiat_value: i) end @@ -639,7 +656,7 @@ defmodule BlockScoutWeb.API.V2.TokenControllerTest do ) check_tokens_pagination( - erc_20_tokens |> Kernel.++(erc_1155_tokens) |> Enum.reverse(), + erc_1155_tokens |> Enum.reverse() |> Kernel.++(erc_20_tokens), conn, %{ "type" => "[erc-20,ERC-1155]" @@ -652,7 +669,6 @@ defmodule BlockScoutWeb.API.V2.TokenControllerTest do for i <- 0..50 do insert(:token, fiat_value: i) end - |> Enum.reverse() check_tokens_pagination(tokens, conn) end @@ -992,7 +1008,7 @@ defmodule BlockScoutWeb.API.V2.TokenControllerTest do assert data = json_response(request, 200) assert compare_item(instance, data) - assert compare_item(transfer.to_address, data["owner"]) + assert Address.checksum(instance.owner_address_hash) == data["owner"]["hash"] end end diff --git a/apps/block_scout_web/test/block_scout_web/controllers/api/v2/transaction_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/api/v2/transaction_controller_test.exs index ede7f16822d1..9fdad1782da8 100644 --- a/apps/block_scout_web/test/block_scout_web/controllers/api/v2/transaction_controller_test.exs +++ b/apps/block_scout_web/test/block_scout_web/controllers/api/v2/transaction_controller_test.exs @@ -8,6 +8,13 @@ defmodule BlockScoutWeb.API.V2.TransactionControllerTest do alias Explorer.Chain.{Address, InternalTransaction, Log, Token, TokenTransfer, Transaction} alias Explorer.Repo + @first_topic_hex_string_1 "0x99e7b0ba56da2819c37c047f0511fd2bf6c9b4e27b4a979a19d6da0f74be8155" + + defp topic(topic_hex_string) do + {:ok, topic} = Explorer.Chain.Hash.Full.cast(topic_hex_string) + topic + end + setup do Supervisor.terminate_child(Explorer.Supervisor, Explorer.Chain.Cache.TransactionsApiV2.child_id()) Supervisor.restart_child(Explorer.Supervisor, Explorer.Chain.Cache.TransactionsApiV2.child_id()) @@ -976,7 +983,7 @@ defmodule BlockScoutWeb.API.V2.TransactionControllerTest do index: 1, block: tx.block, block_number: tx.block_number, - first_topic: "0x99e7b0ba56da2819c37c047f0511fd2bf6c9b4e27b4a979a19d6da0f74be8155", + first_topic: topic(@first_topic_hex_string_1), data: "0x000000000000000000000000dc2b93f3291030f3f7a6d9363ac37757f7ad5c4300000000000000000000000000000000000000000000000000002824369a100000000000000000000000000046b555cb3962bf9533c437cbd04a2f702dfdb999000000000000000000000000000000000000000000000000000014121b4d0800000000000000000000000000faf7a981360c2fab3a5ab7b3d6d8d0cf97a91eb9000000000000000000000000000000000000000000000000000014121b4d0800" ) @@ -1039,7 +1046,7 @@ defmodule BlockScoutWeb.API.V2.TransactionControllerTest do index: 1, block: tx.block, block_number: tx.block_number, - first_topic: "0x99e7b0ba56da2819c37c047f0511fd2bf6c9b4e27b4a979a19d6da0f74be8155", + first_topic: topic(@first_topic_hex_string_1), data: "0x000000000000000000000000dc2b93f3291030f3f7a6d9363ac37757f7ad5c4300000000000000000000000000000000000000000000000000002824369a100000000000000000000000000046b555cb3962bf9533c437cbd04a2f702dfdb999000000000000000000000000000000000000000000000000000014121b4d0800000000000000000000000000faf7a981360c2fab3a5ab7b3d6d8d0cf97a91eb9000000000000000000000000000000000000000000000000000014121b4d0800" ) diff --git a/apps/block_scout_web/test/block_scout_web/controllers/smart_contract_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/smart_contract_controller_test.exs index aec5f0d1d327..8d2889876964 100644 --- a/apps/block_scout_web/test/block_scout_web/controllers/smart_contract_controller_test.exs +++ b/apps/block_scout_web/test/block_scout_web/controllers/smart_contract_controller_test.exs @@ -86,6 +86,8 @@ defmodule BlockScoutWeb.SmartContractControllerTest do contract_code_md5: "123" ) + get_eip1967_implementation_zero_addresses() + path = smart_contract_path(BlockScoutWeb.Endpoint, :index, hash: token_contract_address.hash, @@ -304,4 +306,71 @@ defmodule BlockScoutWeb.SmartContractControllerTest do {:ok, "0x000000000000000000000000cebb2CCCFe291F0c442841cBE9C1D06EED61Ca02"} end) end + + defp mock_empty_logic_storage_pointer_request do + expect(EthereumJSONRPC.Mox, :json_rpc, fn %{ + id: 0, + method: "eth_getStorageAt", + params: [ + _, + "0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc", + "latest" + ] + }, + _options -> + {:ok, "0x0000000000000000000000000000000000000000000000000000000000000000"} + end) + end + + defp mock_empty_beacon_storage_pointer_request(mox) do + expect(mox, :json_rpc, fn %{ + id: 0, + method: "eth_getStorageAt", + params: [ + _, + "0xa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6cb3582b35133d50", + "latest" + ] + }, + _options -> + {:ok, "0x0000000000000000000000000000000000000000000000000000000000000000"} + end) + end + + defp mock_empty_eip_1822_storage_pointer_request(mox) do + expect(mox, :json_rpc, fn %{ + id: 0, + method: "eth_getStorageAt", + params: [ + _, + "0xc5f16f0fcc639fa48a6947836d9850f504798523bf8c9a3a87d5876cf622bcf7", + "latest" + ] + }, + _options -> + {:ok, "0x0000000000000000000000000000000000000000000000000000000000000000"} + end) + end + + defp mock_empty_oz_storage_pointer_request(mox) do + expect(mox, :json_rpc, fn %{ + id: 0, + method: "eth_getStorageAt", + params: [ + _, + "0x7050c9e0f4ca769c69bd3a8ef740bc37934f8e2c036e5a723fd8ee048ed3f8c3", + "latest" + ] + }, + _options -> + {:ok, "0x0000000000000000000000000000000000000000000000000000000000000000"} + end) + end + + def get_eip1967_implementation_zero_addresses do + mock_empty_logic_storage_pointer_request() + |> mock_empty_beacon_storage_pointer_request() + |> mock_empty_oz_storage_pointer_request() + |> mock_empty_eip_1822_storage_pointer_request() + end end diff --git a/apps/block_scout_web/test/block_scout_web/controllers/tokens/inventory_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/tokens/inventory_controller_test.exs index 799b54da797c..e15f85569214 100644 --- a/apps/block_scout_web/test/block_scout_web/controllers/tokens/inventory_controller_test.exs +++ b/apps/block_scout_web/test/block_scout_web/controllers/tokens/inventory_controller_test.exs @@ -126,7 +126,7 @@ defmodule BlockScoutWeb.Tokens.InventoryControllerTest do transaction: transaction, token_contract_address: token.contract_address, token: token, - token_id: 1000 + token_ids: [1000] ) conn = get(conn, token_inventory_path(conn, :index, token.contract_address_hash), %{type: "JSON"}) diff --git a/apps/block_scout_web/test/block_scout_web/controllers/verified_contracts_controller_test.exs b/apps/block_scout_web/test/block_scout_web/controllers/verified_contracts_controller_test.exs index 0a68febfa183..55936934dcdb 100644 --- a/apps/block_scout_web/test/block_scout_web/controllers/verified_contracts_controller_test.exs +++ b/apps/block_scout_web/test/block_scout_web/controllers/verified_contracts_controller_test.exs @@ -65,7 +65,9 @@ defmodule BlockScoutWeb.VerifiedContractsControllerTest do expected_path = verified_contracts_path(conn, :index, %{ smart_contract_id: id, - items_count: "50" + items_count: "50", + coin_balance: nil, + tx_count: nil }) assert Map.get(json_response(conn, 200), "next_page_path") == expected_path diff --git a/apps/block_scout_web/test/block_scout_web/schema/query/token_transfers_test.exs b/apps/block_scout_web/test/block_scout_web/schema/query/token_transfers_test.exs index ea20aa4f4ede..d10a3fdcc639 100644 --- a/apps/block_scout_web/test/block_scout_web/schema/query/token_transfers_test.exs +++ b/apps/block_scout_web/test/block_scout_web/schema/query/token_transfers_test.exs @@ -16,7 +16,6 @@ defmodule BlockScoutWeb.Schema.Query.TokenTransfersTest do amounts block_number log_index - token_id token_ids from_address_hash to_address_hash @@ -45,7 +44,6 @@ defmodule BlockScoutWeb.Schema.Query.TokenTransfersTest do "amounts" => Enum.map(token_transfer.amounts, &to_string/1), "block_number" => token_transfer.block_number, "log_index" => token_transfer.log_index, - "token_id" => token_transfer.token_id, "token_ids" => Enum.map(token_transfer.token_ids, &to_string/1), "from_address_hash" => to_string(token_transfer.from_address_hash), "to_address_hash" => to_string(token_transfer.to_address_hash), @@ -70,7 +68,6 @@ defmodule BlockScoutWeb.Schema.Query.TokenTransfersTest do amount block_number log_index - token_id from_address_hash to_address_hash token_contract_address_hash diff --git a/apps/block_scout_web/test/block_scout_web/views/api/v2/address_view_test.exs b/apps/block_scout_web/test/block_scout_web/views/api/v2/address_view_test.exs new file mode 100644 index 000000000000..7177316bc125 --- /dev/null +++ b/apps/block_scout_web/test/block_scout_web/views/api/v2/address_view_test.exs @@ -0,0 +1,76 @@ +defmodule BlockScoutWeb.API.V2.AddressViewTest do + use BlockScoutWeb.ConnCase, async: true + + import Mox + + alias BlockScoutWeb.API.V2.AddressView + alias Explorer.Repo + + test "for a proxy contract has_methods_read_proxy is true" do + implementation_address = insert(:contract_address) + proxy_address = insert(:contract_address) |> Repo.preload([:token]) + + _proxy_smart_contract = + insert(:smart_contract, + address_hash: proxy_address.hash, + contract_code_md5: "123", + implementation_address_hash: implementation_address.hash + ) + + get_eip1967_implementation_zero_addresses() + + assert AddressView.prepare_address(proxy_address)["has_methods_read_proxy"] == true + end + + def get_eip1967_implementation_zero_addresses do + EthereumJSONRPC.Mox + |> expect(:json_rpc, fn %{ + id: 0, + method: "eth_getStorageAt", + params: [ + _, + "0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc", + "latest" + ] + }, + _options -> + {:ok, "0x0000000000000000000000000000000000000000000000000000000000000000"} + end) + |> expect(:json_rpc, fn %{ + id: 0, + method: "eth_getStorageAt", + params: [ + _, + "0xa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6cb3582b35133d50", + "latest" + ] + }, + _options -> + {:ok, "0x0000000000000000000000000000000000000000000000000000000000000000"} + end) + |> expect(:json_rpc, fn %{ + id: 0, + method: "eth_getStorageAt", + params: [ + _, + "0x7050c9e0f4ca769c69bd3a8ef740bc37934f8e2c036e5a723fd8ee048ed3f8c3", + "latest" + ] + }, + _options -> + {:ok, "0x0000000000000000000000000000000000000000000000000000000000000000"} + end) + |> expect(:json_rpc, fn %{ + id: 0, + method: "eth_getStorageAt", + params: [ + _, + "0xc5f16f0fcc639fa48a6947836d9850f504798523bf8c9a3a87d5876cf622bcf7", + "latest" + ] + }, + _options -> + {:ok, "0x0000000000000000000000000000000000000000000000000000000000000000"} + end) + end +end diff --git a/apps/block_scout_web/test/block_scout_web/views/tokens/helper_test.exs b/apps/block_scout_web/test/block_scout_web/views/tokens/helper_test.exs index e59a85fb76c4..a8815379cedb 100644 --- a/apps/block_scout_web/test/block_scout_web/views/tokens/helper_test.exs +++ b/apps/block_scout_web/test/block_scout_web/views/tokens/helper_test.exs @@ -27,7 +27,7 @@ defmodule BlockScoutWeb.Tokens.HelperTest do test "returns a string with the token_id with ERC-721 token" do token = build(:token, type: "ERC-721", decimals: nil) - token_transfer = build(:token_transfer, token: token, amount: nil, token_id: 1) + token_transfer = build(:token_transfer, token: token, amount: nil, token_ids: [1]) assert Helper.token_transfer_amount(token_transfer) == {:ok, :erc721_instance} end diff --git a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc.ex b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc.ex index abad4cdfb3cd..74d37fe187df 100644 --- a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc.ex +++ b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc.ex @@ -39,6 +39,7 @@ defmodule EthereumJSONRPC do Subscription, Transport, Utility.EndpointAvailabilityObserver, + Utility.RangesHelper, Variant } @@ -205,7 +206,8 @@ defmodule EthereumJSONRPC do filtered_params_in_range = filtered_params |> Enum.filter(fn - %{block_quantity: block_quantity} -> is_block_number_in_range?(block_quantity) + %{block_quantity: block_quantity} -> + block_quantity |> quantity_to_integer() |> RangesHelper.traceable_block_number?() end) id_to_params = id_to_params(filtered_params_in_range) @@ -244,7 +246,7 @@ defmodule EthereumJSONRPC do @spec fetch_beneficiaries([block_number], json_rpc_named_arguments) :: {:ok, FetchedBeneficiaries.t()} | {:error, reason :: term} | :ignore def fetch_beneficiaries(block_numbers, json_rpc_named_arguments) when is_list(block_numbers) do - filtered_block_numbers = are_block_numbers_in_range?(block_numbers) + filtered_block_numbers = RangesHelper.filter_traceable_block_numbers(block_numbers) Keyword.fetch!(json_rpc_named_arguments, :variant).fetch_beneficiaries( filtered_block_numbers, @@ -349,7 +351,7 @@ defmodule EthereumJSONRPC do Fetches internal transactions for entire blocks from variant API. """ def fetch_block_internal_transactions(block_numbers, json_rpc_named_arguments) when is_list(block_numbers) do - filtered_block_numbers = are_block_numbers_in_range?(block_numbers) + filtered_block_numbers = RangesHelper.filter_traceable_block_numbers(block_numbers) Keyword.fetch!(json_rpc_named_arguments, :variant).fetch_block_internal_transactions( filtered_block_numbers, @@ -357,16 +359,6 @@ defmodule EthereumJSONRPC do ) end - def are_block_numbers_in_range?(block_numbers) do - min_block = Application.get_env(:indexer, :trace_first_block) - max_block = Application.get_env(:indexer, :trace_last_block) - - block_numbers - |> Enum.filter(fn block_number -> - block_number >= min_block && if max_block, do: block_number <= max_block, else: true - end) - end - @doc """ Retrieves traces from variant API. """ @@ -459,20 +451,6 @@ defmodule EthereumJSONRPC do end end - @spec is_block_number_in_range?(quantity) :: boolean() - defp is_block_number_in_range?(block_quantity) do - min_block = Application.get_env(:indexer, :trace_first_block) - max_block = Application.get_env(:indexer, :trace_last_block) - block_number = quantity_to_integer(block_quantity) - - if !block_number || - (block_number && block_number >= min_block && if(max_block, do: block_number <= max_block, else: true)) do - true - else - false - end - end - defp maybe_replace_url(url, _replace_url, EthereumJSONRPC.HTTP), do: url defp maybe_replace_url(url, replace_url, _), do: EndpointAvailabilityObserver.maybe_replace_url(url, replace_url) diff --git a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/block.ex b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/block.ex index 88f0213f98aa..a3a32ddc0c9c 100644 --- a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/block.ex +++ b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/block.ex @@ -82,7 +82,7 @@ defmodule EthereumJSONRPC.Block do * `uncles`: `t:list/0` of [uncles](https://bitcoin.stackexchange.com/questions/39329/in-ethereum-what-is-an-uncle-block) `t:EthereumJSONRPC.hash/0`. - * `"baseFeePerGas"` - `t:EthereumJSONRPC.quantity/0` of wei to denote amount of fee burned per unit gas used. Introduced in [EIP-1559](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-1559.md) + * `"baseFeePerGas"` - `t:EthereumJSONRPC.quantity/0` of wei to denote amount of fee burnt per unit gas used. Introduced in [EIP-1559](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-1559.md) * `"withdrawalsRoot"` - `t:EthereumJSONRPC.hash/0` of the root of the withdrawals. #{if Application.compile_env(:explorer, :chain_type) == "rsk" do """ @@ -766,6 +766,11 @@ defmodule EthereumJSONRPC.Block do {key, quantity_to_integer(quantity)} end + # to be merged with clause above ^ + defp entry_to_elixir({key, _quantity}, _block) when key in ~w(blobGasUsed excessBlobGas) do + {:ignore, :ignore} + end + # Size and totalDifficulty may be `nil` for uncle blocks defp entry_to_elixir({key, nil}, _block) when key in ~w(size totalDifficulty) do {key, nil} @@ -783,8 +788,8 @@ defmodule EthereumJSONRPC.Block do {key, timestamp_to_datetime(timestamp)} end - defp entry_to_elixir({"transactions" = key, transactions}, _block) do - {key, Transactions.to_elixir(transactions)} + defp entry_to_elixir({"transactions" = key, transactions}, %{"timestamp" => block_timestamp}) do + {key, Transactions.to_elixir(transactions, timestamp_to_datetime(block_timestamp))} end defp entry_to_elixir({"withdrawals" = key, nil}, _block) do diff --git a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/encoder.ex b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/encoder.ex index 37573679aa51..e667ce8eaa89 100644 --- a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/encoder.ex +++ b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/encoder.ex @@ -13,7 +13,7 @@ defmodule EthereumJSONRPC.Encoder do """ @spec encode_function_call(ABI.FunctionSelector.t(), [term()]) :: String.t() def encode_function_call(function_selector, args) when is_list(args) do - parsed_args = parse_args(args) + parsed_args = parse_args(args, function_selector.types) encoded_args = function_selector @@ -25,16 +25,22 @@ defmodule EthereumJSONRPC.Encoder do def encode_function_call(function_selector, args), do: encode_function_call(function_selector, [args]) - defp parse_args(args) when is_list(args) do + defp parse_args(args, types) when is_list(args) do args - |> Enum.map(&parse_args/1) + |> Enum.zip(types) + |> Enum.map(fn {arg, type} -> + parse_args(arg, type) + end) end - defp parse_args(<<"0x", hexadecimal_digits::binary>>), do: Base.decode16!(hexadecimal_digits, case: :mixed) + defp parse_args(<>, type) when type in [:string, "string"], + do: hexadecimal_digits |> Base.encode16() |> try_to_decode() + + defp parse_args(<<"0x", hexadecimal_digits::binary>>, _type), do: Base.decode16!(hexadecimal_digits, case: :mixed) - defp parse_args(<>), do: try_to_decode(hexadecimal_digits) + defp parse_args(<>, _type), do: try_to_decode(hexadecimal_digits) - defp parse_args(arg), do: arg + defp parse_args(arg, _type), do: arg defp try_to_decode(hexadecimal_digits) do case Base.decode16(hexadecimal_digits, case: :mixed) do diff --git a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/geth.ex b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/geth.ex index 5ecf02ed024f..790f2c84afd3 100644 --- a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/geth.ex +++ b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/geth.ex @@ -292,56 +292,53 @@ defmodule EthereumJSONRPC.Geth do defp parse_call_tracer_calls([], acc, _trace_address, _inner?), do: acc defp parse_call_tracer_calls({%{"type" => 0}, _}, acc, _trace_address, _inner?), do: acc - defp parse_call_tracer_calls({%{"type" => "STOP"}, _}, [last | acc], _trace_address, _inner?) do + defp parse_call_tracer_calls({%{"type" => type}, _}, [last | acc], _trace_address, _inner?) + when type in ["STOP", "stop"] do [Map.put(last, "error", "execution stopped") | acc] end - defp parse_call_tracer_calls( - {%{"type" => type, "from" => from} = call, index}, - acc, - trace_address, - inner? - ) - when type in ~w(CALL CALLCODE DELEGATECALL STATICCALL CREATE CREATE2 SELFDESTRUCT REVERT STOP) do - new_trace_address = [index | trace_address] - - formatted_call = - %{ - "type" => if(type in ~w(CALL CALLCODE DELEGATECALL STATICCALL), do: "call", else: String.downcase(type)), - "callType" => String.downcase(type), - "from" => from, - "to" => Map.get(call, "to", "0x"), - "createdContractAddressHash" => Map.get(call, "to", "0x"), - "value" => Map.get(call, "value", "0x0"), - "gas" => Map.get(call, "gas", "0x0"), - "gasUsed" => Map.get(call, "gasUsed", "0x0"), - "input" => Map.get(call, "input", "0x"), - "init" => Map.get(call, "input", "0x"), - "createdContractCode" => Map.get(call, "output", "0x"), - "traceAddress" => if(inner?, do: Enum.reverse(new_trace_address), else: []), - "error" => call["error"] - } - |> case do - %{"error" => nil} = ok_call -> - ok_call - |> Map.delete("error") - # to handle staticcall, all other cases handled by EthereumJSONRPC.Geth.Call.elixir_to_internal_transaction_params/1 - |> Map.put("output", Map.get(call, "output", "0x")) - - error_call -> - error_call - end - - parse_call_tracer_calls( - Map.get(call, "calls", []), - [formatted_call | acc], - if(inner?, do: new_trace_address, else: []) - ) - end + defp parse_call_tracer_calls({%{"type" => upcase_type, "from" => from} = call, index}, acc, trace_address, inner?) do + case String.downcase(upcase_type) do + type when type in ~w(call callcode delegatecall staticcall create create2 selfdestruct revert stop) -> + new_trace_address = [index | trace_address] - defp parse_call_tracer_calls({call, _}, acc, _trace_address, _inner?) do - Logger.warning("Call from a callTracer with an unknown type: #{inspect(call)}") - acc + formatted_call = + %{ + "type" => if(type in ~w(call callcode delegatecall staticcall), do: "call", else: type), + "callType" => type, + "from" => from, + "to" => Map.get(call, "to", "0x"), + "createdContractAddressHash" => Map.get(call, "to", "0x"), + "value" => Map.get(call, "value", "0x0"), + "gas" => Map.get(call, "gas", "0x0"), + "gasUsed" => Map.get(call, "gasUsed", "0x0"), + "input" => Map.get(call, "input", "0x"), + "init" => Map.get(call, "input", "0x"), + "createdContractCode" => Map.get(call, "output", "0x"), + "traceAddress" => if(inner?, do: Enum.reverse(new_trace_address), else: []), + "error" => call["error"] + } + |> case do + %{"error" => nil} = ok_call -> + ok_call + |> Map.delete("error") + # to handle staticcall, all other cases handled by EthereumJSONRPC.Geth.Call.elixir_to_internal_transaction_params/1 + |> Map.put("output", Map.get(call, "output", "0x")) + + error_call -> + error_call + end + + parse_call_tracer_calls( + Map.get(call, "calls", []), + [formatted_call | acc], + if(inner?, do: new_trace_address, else: []) + ) + + _unknown_type -> + Logger.warning("Call from a callTracer with an unknown type: #{inspect(call)}") + acc + end end defp parse_call_tracer_calls(calls, acc, trace_address, _inner) when is_list(calls) do diff --git a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/log.ex b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/log.ex index ecda58364425..f3c6a88662c5 100644 --- a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/log.ex +++ b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/log.ex @@ -33,8 +33,7 @@ defmodule EthereumJSONRPC.Log do ...> "topics" => ["0x600bcf04a13e752d1e3670a5a9f1c21177ca2a93c6f5391d4f1298d098097c22"], ...> "transactionHash" => "0x53bd884872de3e488692881baeec262e7b95234d3965248c39fe992fffd433e5", ...> "transactionIndex" => 0, - ...> "transactionLogIndex" => 0, - ...> "type" => "mined" + ...> "transactionLogIndex" => 0 ...> } ...> ) %{ @@ -47,12 +46,9 @@ defmodule EthereumJSONRPC.Log do index: 0, second_topic: nil, third_topic: nil, - transaction_hash: "0x53bd884872de3e488692881baeec262e7b95234d3965248c39fe992fffd433e5", - type: "mined" + transaction_hash: "0x53bd884872de3e488692881baeec262e7b95234d3965248c39fe992fffd433e5" } - Geth does not supply a `"type"` - iex> EthereumJSONRPC.Log.elixir_to_params( ...> %{ ...> "address" => "0xda8b3276cde6d768a44b9dac659faa339a41ac55", @@ -82,17 +78,15 @@ defmodule EthereumJSONRPC.Log do } """ - def elixir_to_params( - %{ - "address" => address_hash, - "blockNumber" => block_number, - "blockHash" => block_hash, - "data" => data, - "logIndex" => index, - "topics" => topics, - "transactionHash" => transaction_hash - } = elixir - ) do + def elixir_to_params(%{ + "address" => address_hash, + "blockNumber" => block_number, + "blockHash" => block_hash, + "data" => data, + "logIndex" => index, + "topics" => topics, + "transactionHash" => transaction_hash + }) do %{ address_hash: address_hash, block_number: block_number, @@ -102,7 +96,6 @@ defmodule EthereumJSONRPC.Log do transaction_hash: transaction_hash } |> put_topics(topics) - |> put_type(elixir) end @doc """ @@ -118,8 +111,7 @@ defmodule EthereumJSONRPC.Log do ...> "topics" => ["0x600bcf04a13e752d1e3670a5a9f1c21177ca2a93c6f5391d4f1298d098097c22"], ...> "transactionHash" => "0x53bd884872de3e488692881baeec262e7b95234d3965248c39fe992fffd433e5", ...> "transactionIndex" => "0x0", - ...> "transactionLogIndex" => "0x0", - ...> "type" => "mined" + ...> "transactionLogIndex" => "0x0" ...> } ...> ) %{ @@ -131,8 +123,7 @@ defmodule EthereumJSONRPC.Log do "topics" => ["0x600bcf04a13e752d1e3670a5a9f1c21177ca2a93c6f5391d4f1298d098097c22"], "transactionHash" => "0x53bd884872de3e488692881baeec262e7b95234d3965248c39fe992fffd433e5", "transactionIndex" => 0, - "transactionLogIndex" => 0, - "type" => "mined" + "transactionLogIndex" => 0 } Geth includes a `"removed"` key @@ -172,7 +163,7 @@ defmodule EthereumJSONRPC.Log do end defp entry_to_elixir({key, _} = entry) - when key in ~w(address blockHash data removed topics transactionHash type timestamp), + when key in ~w(address blockHash data removed topics transactionHash timestamp), do: entry defp entry_to_elixir({key, quantity}) when key in ~w(blockNumber logIndex transactionIndex transactionLogIndex) do @@ -190,10 +181,4 @@ defmodule EthereumJSONRPC.Log do |> Map.put(:third_topic, Enum.at(topics, 2)) |> Map.put(:fourth_topic, Enum.at(topics, 3)) end - - defp put_type(params, %{"type" => type}) do - Map.put(params, :type, type) - end - - defp put_type(params, _), do: params end diff --git a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/receipt.ex b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/receipt.ex index 64aa388312db..b9c5f6f0c851 100644 --- a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/receipt.ex +++ b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/receipt.ex @@ -309,7 +309,7 @@ defmodule EthereumJSONRPC.Receipt do end # Arbitrum fields - defp entry_to_elixir({key, _}) when key in ~w(returnData returnCode feeStats l1BlockNumber) do + defp entry_to_elixir({key, _}) when key in ~w(returnData returnCode feeStats l1BlockNumber gasUsedForL1) do :ignore end @@ -328,6 +328,11 @@ defmodule EthereumJSONRPC.Receipt do :ignore end + # EIP-4844 transaction receipt fields + defp entry_to_elixir({key, _}) when key in ~w(blobGasUsed blobGasPrice) do + :ignore + end + defp entry_to_elixir({key, value}) do {:error, {:unknown_key, %{key: key, value: value}}} end diff --git a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/receipts.ex b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/receipts.ex index 5f03f3e16f99..40720f1908ea 100644 --- a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/receipts.ex +++ b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/receipts.ex @@ -32,8 +32,7 @@ defmodule EthereumJSONRPC.Receipts do ...> "topics" => ["0x600bcf04a13e752d1e3670a5a9f1c21177ca2a93c6f5391d4f1298d098097c22"], ...> "transactionHash" => "0x53bd884872de3e488692881baeec262e7b95234d3965248c39fe992fffd433e5", ...> "transactionIndex" => 0, - ...> "transactionLogIndex" => 0, - ...> "type" => "mined" + ...> "transactionLogIndex" => 0 ...> } ...> ], ...> "logsBloom" => "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000200000000000000000000020000000000000000200000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", @@ -53,8 +52,7 @@ defmodule EthereumJSONRPC.Receipts do "topics" => ["0x600bcf04a13e752d1e3670a5a9f1c21177ca2a93c6f5391d4f1298d098097c22"], "transactionHash" => "0x53bd884872de3e488692881baeec262e7b95234d3965248c39fe992fffd433e5", "transactionIndex" => 0, - "transactionLogIndex" => 0, - "type" => "mined" + "transactionLogIndex" => 0 } ] @@ -84,8 +82,7 @@ defmodule EthereumJSONRPC.Receipts do ...> "topics" => ["0x600bcf04a13e752d1e3670a5a9f1c21177ca2a93c6f5391d4f1298d098097c22"], ...> "transactionHash" => "0x53bd884872de3e488692881baeec262e7b95234d3965248c39fe992fffd433e5", ...> "transactionIndex" => 0, - ...> "transactionLogIndex" => 0, - ...> "type" => "mined" + ...> "transactionLogIndex" => 0 ...> } ...> ], ...> "logsBloom" => "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000200000000000000000000020000000000000000200000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", @@ -165,8 +162,7 @@ defmodule EthereumJSONRPC.Receipts do ...> "topics" => ["0x600bcf04a13e752d1e3670a5a9f1c21177ca2a93c6f5391d4f1298d098097c22"], ...> "transactionHash" => "0x53bd884872de3e488692881baeec262e7b95234d3965248c39fe992fffd433e5", ...> "transactionIndex" => "0x0", - ...> "transactionLogIndex" => "0x0", - ...> "type" => "mined" + ...> "transactionLogIndex" => "0x0" ...> } ...> ], ...> "logsBloom" => "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000200000000000000000000020000000000000000200000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", @@ -193,8 +189,7 @@ defmodule EthereumJSONRPC.Receipts do "topics" => ["0x600bcf04a13e752d1e3670a5a9f1c21177ca2a93c6f5391d4f1298d098097c22"], "transactionHash" => "0x53bd884872de3e488692881baeec262e7b95234d3965248c39fe992fffd433e5", "transactionIndex" => 0, - "transactionLogIndex" => 0, - "type" => "mined" + "transactionLogIndex" => 0 } ], "logsBloom" => "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000200000000000000000000020000000000000000200000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", diff --git a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/request_coordinator.ex b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/request_coordinator.ex index a80db36feb5f..77ee7d3906bd 100644 --- a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/request_coordinator.ex +++ b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/request_coordinator.ex @@ -58,7 +58,7 @@ defmodule EthereumJSONRPC.RequestCoordinator do alias EthereumJSONRPC.{RollingWindow, Tracer, Transport} - @error_key :throttleable_error_count + @error_key_base "throttleable_error_count" @throttle_key :throttle_requests_count @doc """ @@ -73,7 +73,9 @@ defmodule EthereumJSONRPC.RequestCoordinator do @spec perform(Transport.batch_request(), Transport.t(), Transport.options(), non_neg_integer()) :: {:ok, Transport.batch_response()} | {:error, term()} def perform(request, transport, transport_options, throttle_timeout) do - sleep_time = sleep_time() + request_method = request_method(request) + + sleep_time = sleep_time(request_method) if sleep_time <= throttle_timeout do :timer.sleep(sleep_time) @@ -85,7 +87,7 @@ defmodule EthereumJSONRPC.RequestCoordinator do trace_request(request, fn -> request |> transport.json_rpc(transport_options) - |> handle_transport_response() + |> handle_transport_response(request_method) end) :error -> @@ -110,19 +112,24 @@ defmodule EthereumJSONRPC.RequestCoordinator do defp trace_request(_, fun), do: fun.() - defp handle_transport_response({:error, {error_type, _}} = error) when error_type in [:bad_gateway, :bad_response] do - RollingWindow.inc(table(), @error_key) + defp request_method([request | _]), do: request_method(request) + defp request_method(%{method: method}), do: method + defp request_method(_), do: nil + + defp handle_transport_response({:error, {error_type, _}} = error, method) + when error_type in [:bad_gateway, :bad_response] do + RollingWindow.inc(table(), method_error_key(method)) inc_throttle_table() error end - defp handle_transport_response({:error, :timeout} = error) do - RollingWindow.inc(table(), @error_key) + defp handle_transport_response({:error, :timeout} = error, method) do + RollingWindow.inc(table(), method_error_key(method)) inc_throttle_table() error end - defp handle_transport_response(response) do + defp handle_transport_response(response, _method) do inc_throttle_table() response end @@ -154,14 +161,16 @@ defmodule EthereumJSONRPC.RequestCoordinator do end end - defp sleep_time do - wait_coefficient = RollingWindow.count(table(), @error_key) + defp sleep_time(request_method) do + wait_coefficient = RollingWindow.count(table(), method_error_key(request_method)) jitter = :rand.uniform(config!(:max_jitter)) wait_per_timeout = config!(:wait_per_timeout) wait_coefficient * (wait_per_timeout + jitter) end + defp method_error_key(method), do: :"#{@error_key_base}_#{method}" + defp table do :rolling_window_opts |> config!() diff --git a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/transaction.ex b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/transaction.ex index 1e3f8f1cf954..2f11da852095 100644 --- a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/transaction.ex +++ b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/transaction.ex @@ -237,11 +237,10 @@ defmodule EthereumJSONRPC.Transaction do result end - if transaction["creates"] do - Map.put(result, :created_contract_address_hash, transaction["creates"]) - else - result - end + put_if_present(transaction, result, [ + {"creates", :created_contract_address_hash}, + {"block_timestamp", :block_timestamp} + ]) end def elixir_to_params( @@ -286,11 +285,10 @@ defmodule EthereumJSONRPC.Transaction do max_fee_per_gas: max_fee_per_gas } - if transaction["creates"] do - Map.put(result, :created_contract_address_hash, transaction["creates"]) - else - result - end + put_if_present(transaction, result, [ + {"creates", :created_contract_address_hash}, + {"block_timestamp", :block_timestamp} + ]) end # txpool_content method on Erigon node returns tx data @@ -336,11 +334,10 @@ defmodule EthereumJSONRPC.Transaction do max_fee_per_gas: max_fee_per_gas } - if transaction["creates"] do - Map.put(result, :created_contract_address_hash, transaction["creates"]) - else - result - end + put_if_present(transaction, result, [ + {"creates", :created_contract_address_hash}, + {"block_timestamp", :block_timestamp} + ]) end # this is for Suave chain (handles `executionNode` and `requestRecord` fields without EIP-1559 fields) @@ -407,11 +404,10 @@ defmodule EthereumJSONRPC.Transaction do result end - if transaction["creates"] do - Map.put(result, :created_contract_address_hash, transaction["creates"]) - else - result - end + put_if_present(transaction, result, [ + {"creates", :created_contract_address_hash}, + {"block_timestamp", :block_timestamp} + ]) end def elixir_to_params( @@ -452,11 +448,10 @@ defmodule EthereumJSONRPC.Transaction do type: type } - if transaction["creates"] do - Map.put(result, :created_contract_address_hash, transaction["creates"]) - else - result - end + put_if_present(transaction, result, [ + {"creates", :created_contract_address_hash}, + {"block_timestamp", :block_timestamp} + ]) end def elixir_to_params( @@ -495,11 +490,10 @@ defmodule EthereumJSONRPC.Transaction do transaction_index: index } - if transaction["creates"] do - Map.put(result, :created_contract_address_hash, transaction["creates"]) - else - result - end + put_if_present(transaction, result, [ + {"creates", :created_contract_address_hash}, + {"block_timestamp", :block_timestamp} + ]) end @doc """ @@ -580,11 +574,14 @@ defmodule EthereumJSONRPC.Transaction do } """ - def to_elixir(transaction) when is_map(transaction) do - Enum.into(transaction, %{}, &entry_to_elixir/1) + def to_elixir(transaction, block_timestamp \\ nil) + + def to_elixir(transaction, block_timestamp) when is_map(transaction) do + initial = (block_timestamp && %{"block_timestamp" => block_timestamp}) || %{} + Enum.into(transaction, initial, &entry_to_elixir/1) end - def to_elixir(transaction) when is_binary(transaction) do + def to_elixir(transaction, _block_timestamp) when is_binary(transaction) do nil end @@ -621,6 +618,16 @@ defmodule EthereumJSONRPC.Transaction do {key, quantity_to_integer(quantity)} end + # to be merged with the clause above ^ + defp entry_to_elixir({"maxFeePerBlobGas", _value}) do + {nil, nil} + end + + # EIP-4844 specific field with value of type of list of hashes + defp entry_to_elixir({"blobVersionedHashes", _value}) do + {nil, nil} + end + # as always ganache has it's own vision on JSON RPC standard defp entry_to_elixir({key, nil}) when key in ~w(r s v) do {key, 0} @@ -648,4 +655,16 @@ defmodule EthereumJSONRPC.Transaction do defp entry_to_elixir(_) do {nil, nil} end + + defp put_if_present(transaction, result, keys) do + Enum.reduce(keys, result, fn {from_key, to_key}, acc -> + value = transaction[from_key] + + if value do + Map.put(acc, to_key, value) + else + acc + end + end) + end end diff --git a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/transactions.ex b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/transactions.ex index 9b3937873932..ecdf103b4e89 100644 --- a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/transactions.ex +++ b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/transactions.ex @@ -151,9 +151,9 @@ defmodule EthereumJSONRPC.Transactions do ] """ - def to_elixir(transactions) when is_list(transactions) do + def to_elixir(transactions, block_timestamp \\ nil) when is_list(transactions) do transactions - |> Enum.map(&Transaction.to_elixir/1) + |> Enum.map(&Transaction.to_elixir(&1, block_timestamp)) |> Enum.filter(&(!is_nil(&1))) end end diff --git a/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/utility/ranges_helper.ex b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/utility/ranges_helper.ex new file mode 100644 index 000000000000..f7220b044d0b --- /dev/null +++ b/apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/utility/ranges_helper.ex @@ -0,0 +1,116 @@ +# credo:disable-for-this-file +defmodule EthereumJSONRPC.Utility.RangesHelper do + @moduledoc """ + Helper for ranges manipulations. + """ + + @default_trace_block_ranges "0..latest" + + @spec traceable_block_number?(integer() | nil) :: boolean() + def traceable_block_number?(block_number) do + if trace_ranges_present?() do + number_in_ranges?(block_number, get_trace_block_ranges()) + else + true + end + end + + @spec filter_traceable_block_numbers([integer()]) :: [integer()] + def filter_traceable_block_numbers(block_numbers) do + if trace_ranges_present?() do + trace_block_ranges = get_trace_block_ranges() + Enum.filter(block_numbers, &number_in_ranges?(&1, trace_block_ranges)) + else + block_numbers + end + end + + @spec trace_ranges_present? :: boolean() + def trace_ranges_present? do + Application.get_env(:indexer, :trace_block_ranges) != @default_trace_block_ranges + end + + @spec get_trace_block_ranges :: [Range.t() | integer()] + def get_trace_block_ranges do + :indexer + |> Application.get_env(:trace_block_ranges) + |> parse_block_ranges() + end + + @spec parse_block_ranges(binary()) :: [Range.t() | integer()] + def parse_block_ranges(block_ranges_string) do + block_ranges_string + |> String.split(",") + |> Enum.map(fn string_range -> + case String.split(string_range, "..") do + [from_string, "latest"] -> + parse_integer(from_string) + + [from_string, to_string] -> + get_from_to(from_string, to_string) + + _ -> + nil + end + end) + |> sanitize_ranges() + end + + defp number_in_ranges?(number, ranges) do + Enum.reduce_while(ranges, false, fn + _from.._to = range, _acc -> if number in range, do: {:halt, true}, else: {:cont, false} + num_to_latest, _acc -> if number >= num_to_latest, do: {:halt, true}, else: {:cont, false} + end) + end + + defp get_from_to(from_string, to_string) do + with {from, ""} <- Integer.parse(from_string), + {to, ""} <- Integer.parse(to_string) do + if from <= to, do: from..to, else: nil + else + _ -> nil + end + end + + @spec sanitize_ranges([Range.t() | integer()]) :: [Range.t() | integer()] + def sanitize_ranges(ranges) do + ranges + |> Enum.reject(&is_nil/1) + |> Enum.sort_by( + fn + from.._to -> from + el -> el + end, + :asc + ) + |> Enum.chunk_while( + nil, + fn + _from.._to = chunk, nil -> + {:cont, chunk} + + _ch_from..ch_to = chunk, acc_from..acc_to = acc -> + if Range.disjoint?(chunk, acc), + do: {:cont, acc, chunk}, + else: {:cont, acc_from..max(ch_to, acc_to)} + + num, nil -> + {:halt, num} + + num, acc_from.._ = acc -> + if Range.disjoint?(num..num, acc), do: {:cont, acc, num}, else: {:halt, acc_from} + + _, num -> + {:halt, num} + end, + fn remainder -> {:cont, remainder, nil} end + ) + end + + defp parse_integer(string) do + case Integer.parse(string) do + {number, ""} -> number + _ -> nil + end + end +end diff --git a/apps/ethereum_jsonrpc/mix.exs b/apps/ethereum_jsonrpc/mix.exs index 20aab2ed33db..d253435df1af 100644 --- a/apps/ethereum_jsonrpc/mix.exs +++ b/apps/ethereum_jsonrpc/mix.exs @@ -23,7 +23,7 @@ defmodule EthereumJsonrpc.MixProject do dialyzer: :test ], start_permanent: Mix.env() == :prod, - version: "5.3.2" + version: "6.0.0" ] end diff --git a/apps/ethereum_jsonrpc/test/ethereum_jsonrpc/encoder_test.exs b/apps/ethereum_jsonrpc/test/ethereum_jsonrpc/encoder_test.exs index 38f87e08af4c..7539b8e582ba 100644 --- a/apps/ethereum_jsonrpc/test/ethereum_jsonrpc/encoder_test.exs +++ b/apps/ethereum_jsonrpc/test/ethereum_jsonrpc/encoder_test.exs @@ -27,6 +27,38 @@ defmodule EthereumJSONRPC.EncoderTest do "0x9507d39a000000000000000000000000000000000000000000000000000000000000000a" end + test "generates the correct encoding with string argument" do + function_selector = %ABI.FunctionSelector{ + function: "isNewsletterCoverFullyClaimed", + input_names: ["newsletterId"], + inputs_indexed: nil, + return_names: [""], + returns: [:bool], + state_mutability: :view, + type: :function, + types: [:string] + } + + assert Encoder.encode_function_call(function_selector, ["6564f5623e2a9f0001cb7fee"]) == + "0xa07a712d000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000183635363466353632336532613966303030316362376665650000000000000000" + end + + test "generates the correct encoding with string started with 0x" do + function_selector = %ABI.FunctionSelector{ + function: "isNewsletterCoverFullyClaimed", + input_names: ["newsletterId"], + inputs_indexed: nil, + return_names: [""], + returns: [:bool], + state_mutability: :view, + type: :function, + types: [:string] + } + + assert Encoder.encode_function_call(function_selector, ["0x123"]) == + "0xa07a712d000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000053078313233000000000000000000000000000000000000000000000000000000" + end + test "generates the correct encoding with addresses arguments" do function_selector = %ABI.FunctionSelector{ function: "tokens", diff --git a/apps/ethereum_jsonrpc/test/ethereum_jsonrpc/geth_test.exs b/apps/ethereum_jsonrpc/test/ethereum_jsonrpc/geth_test.exs index 853a5643ae02..a379ac57a816 100644 --- a/apps/ethereum_jsonrpc/test/ethereum_jsonrpc/geth_test.exs +++ b/apps/ethereum_jsonrpc/test/ethereum_jsonrpc/geth_test.exs @@ -5,16 +5,14 @@ defmodule EthereumJSONRPC.GethTest do alias EthereumJSONRPC.Geth - @moduletag :no_nethermind - setup :verify_on_exit! describe "fetch_internal_transactions/2" do # Infura Mainnet does not support debug_traceTransaction, so this cannot be tested expect in Mox setup do - EthereumJSONRPC.Case.Geth.Mox.setup() initial_env = Application.get_all_env(:ethereum_jsonrpc) on_exit(fn -> Application.put_all_env([{:ethereum_jsonrpc, initial_env}]) end) + EthereumJSONRPC.Case.Geth.Mox.setup() end setup :verify_on_exit! @@ -26,7 +24,7 @@ defmodule EthereumJSONRPC.GethTest do transaction_hash = "0x32b17f27ddb546eab3c4c33f31eb22c1cb992d4ccc50dae26922805b717efe5c" tracer = File.read!("priv/js/ethereum_jsonrpc/geth/debug_traceTransaction/tracer.js") - expect(EthereumJSONRPC.Mox, :json_rpc, fn [%{id: id, params: [^transaction_hash, %{tracer: ^tracer}]}], _ -> + expect(EthereumJSONRPC.Mox, :json_rpc, fn [%{id: id, params: [^transaction_hash, %{"tracer" => ^tracer}]}], _ -> {:ok, [ %{ @@ -49,7 +47,7 @@ defmodule EthereumJSONRPC.GethTest do ]} end) - Application.put_env(:ethereum_jsonrpc, Geth, tracer: "js") + Application.put_env(:ethereum_jsonrpc, Geth, tracer: "js", debug_trace_transaction_timeout: "5s") assert {:ok, [ @@ -98,7 +96,7 @@ defmodule EthereumJSONRPC.GethTest do tracer = File.read!("priv/js/ethereum_jsonrpc/geth/debug_traceTransaction/tracer.js") expect(EthereumJSONRPC.Mox, :json_rpc, 1, fn - [%{id: id, params: [^transaction_hash, %{tracer: "callTracer"}]}], _ -> + [%{id: id, params: [^transaction_hash, %{"tracer" => "callTracer"}]}], _ -> {:ok, [ %{ @@ -222,7 +220,7 @@ defmodule EthereumJSONRPC.GethTest do end) expect(EthereumJSONRPC.Mox, :json_rpc, 1, fn - [%{id: id, params: [^transaction_hash, %{tracer: ^tracer}]}], _ -> + [%{id: id, params: [^transaction_hash, %{"tracer" => ^tracer}]}], _ -> {:ok, [ %{ @@ -359,9 +357,11 @@ defmodule EthereumJSONRPC.GethTest do ]} end) + Application.put_env(:ethereum_jsonrpc, Geth, tracer: "call_tracer", debug_trace_transaction_timeout: "5s") + call_tracer_internal_txs = Geth.fetch_internal_transactions([transaction_params], json_rpc_named_arguments) - Application.put_env(:ethereum_jsonrpc, Geth, tracer: "js") + Application.put_env(:ethereum_jsonrpc, Geth, tracer: "js", debug_trace_transaction_timeout: "5s") assert call_tracer_internal_txs == Geth.fetch_internal_transactions([transaction_params], json_rpc_named_arguments) @@ -383,7 +383,7 @@ defmodule EthereumJSONRPC.GethTest do tracer = File.read!("priv/js/ethereum_jsonrpc/geth/debug_traceTransaction/tracer.js") expect(EthereumJSONRPC.Mox, :json_rpc, 1, fn - [%{id: id, params: [^transaction_hash, %{tracer: "callTracer"}]}], _ -> + [%{id: id, params: [^transaction_hash, %{"tracer" => "callTracer"}]}], _ -> {:ok, [ %{ @@ -405,7 +405,7 @@ defmodule EthereumJSONRPC.GethTest do end) expect(EthereumJSONRPC.Mox, :json_rpc, 1, fn - [%{id: id, params: [^transaction_hash, %{tracer: ^tracer}]}], _ -> + [%{id: id, params: [^transaction_hash, %{"tracer" => ^tracer}]}], _ -> {:ok, [ %{ @@ -429,9 +429,11 @@ defmodule EthereumJSONRPC.GethTest do ]} end) + Application.put_env(:ethereum_jsonrpc, Geth, tracer: "call_tracer", debug_trace_transaction_timeout: "5s") + call_tracer_internal_txs = Geth.fetch_internal_transactions([transaction_params], json_rpc_named_arguments) - Application.put_env(:ethereum_jsonrpc, Geth, tracer: "js") + Application.put_env(:ethereum_jsonrpc, Geth, tracer: "js", debug_trace_transaction_timeout: "5s") assert call_tracer_internal_txs == Geth.fetch_internal_transactions([transaction_params], json_rpc_named_arguments) @@ -467,6 +469,8 @@ defmodule EthereumJSONRPC.GethTest do ]} end) + Application.put_env(:ethereum_jsonrpc, Geth, tracer: "call_tracer", debug_trace_transaction_timeout: "5s") + assert {:ok, [ %{ @@ -482,6 +486,72 @@ defmodule EthereumJSONRPC.GethTest do } ]} = Geth.fetch_internal_transactions([transaction_params], json_rpc_named_arguments) end + + test "uppercase type parsing result is the same as lowercase", %{ + json_rpc_named_arguments: json_rpc_named_arguments + } do + transaction_hash = "0xb342cafc6ac552c3be2090561453204c8784caf025ac8267320834e4cd163d96" + block_number = 3_287_375 + transaction_index = 13 + + transaction_params = %{ + block_number: block_number, + transaction_index: transaction_index, + hash_data: transaction_hash + } + + expect(EthereumJSONRPC.Mox, :json_rpc, 1, fn + [%{id: id, params: [^transaction_hash, %{"tracer" => "callTracer"}]}], _ -> + {:ok, + [ + %{ + id: id, + result: %{ + "type" => "CREATE", + "from" => "0x117b358218da5a4f647072ddb50ded038ed63d17", + "to" => "0x205a6b72ce16736c9d87172568a9c0cb9304de0d", + "value" => "0x0", + "gas" => "0x106f5", + "gasUsed" => "0x106f5", + "input" => + "0x608060405234801561001057600080fd5b50610150806100206000396000f3fe608060405234801561001057600080fd5b50600436106100365760003560e01c80632e64cec11461003b5780636057361d14610059575b600080fd5b610043610075565b60405161005091906100d9565b60405180910390f35b610073600480360381019061006e919061009d565b61007e565b005b60008054905090565b8060008190555050565b60008135905061009781610103565b92915050565b6000602082840312156100b3576100b26100fe565b5b60006100c184828501610088565b91505092915050565b6100d3816100f4565b82525050565b60006020820190506100ee60008301846100ca565b92915050565b6000819050919050565b600080fd5b61010c816100f4565b811461011757600080fd5b5056fea26469706673582212209a159a4f3847890f10bfb87871a61eba91c5dbf5ee3cf6398207e292eee22a1664736f6c63430008070033", + "output" => + "0x608060405234801561001057600080fd5b50600436106100365760003560e01c80632e64cec11461003b5780636057361d14610059575b600080fd5b610043610075565b60405161005091906100d9565b60405180910390f35b610073600480360381019061006e919061009d565b61007e565b005b60008054905090565b8060008190555050565b60008135905061009781610103565b92915050565b6000602082840312156100b3576100b26100fe565b5b60006100c184828501610088565b91505092915050565b6100d3816100f4565b82525050565b60006020820190506100ee60008301846100ca565b92915050565b6000819050919050565b600080fd5b61010c816100f4565b811461011757600080fd5b5056fea26469706673582212209a159a4f3847890f10bfb87871a61eba91c5dbf5ee3cf6398207e292eee22a1664736f6c63430008070033" + } + } + ]} + end) + + Application.put_env(:ethereum_jsonrpc, Geth, tracer: "call_tracer", debug_trace_transaction_timeout: "5s") + + uppercase_result = Geth.fetch_internal_transactions([transaction_params], json_rpc_named_arguments) + + expect(EthereumJSONRPC.Mox, :json_rpc, 1, fn + [%{id: id, params: [^transaction_hash, %{"tracer" => "callTracer"}]}], _ -> + {:ok, + [ + %{ + id: id, + result: %{ + "type" => "create", + "from" => "0x117b358218da5a4f647072ddb50ded038ed63d17", + "to" => "0x205a6b72ce16736c9d87172568a9c0cb9304de0d", + "value" => "0x0", + "gas" => "0x106f5", + "gasUsed" => "0x106f5", + "input" => + "0x608060405234801561001057600080fd5b50610150806100206000396000f3fe608060405234801561001057600080fd5b50600436106100365760003560e01c80632e64cec11461003b5780636057361d14610059575b600080fd5b610043610075565b60405161005091906100d9565b60405180910390f35b610073600480360381019061006e919061009d565b61007e565b005b60008054905090565b8060008190555050565b60008135905061009781610103565b92915050565b6000602082840312156100b3576100b26100fe565b5b60006100c184828501610088565b91505092915050565b6100d3816100f4565b82525050565b60006020820190506100ee60008301846100ca565b92915050565b6000819050919050565b600080fd5b61010c816100f4565b811461011757600080fd5b5056fea26469706673582212209a159a4f3847890f10bfb87871a61eba91c5dbf5ee3cf6398207e292eee22a1664736f6c63430008070033", + "output" => + "0x608060405234801561001057600080fd5b50600436106100365760003560e01c80632e64cec11461003b5780636057361d14610059575b600080fd5b610043610075565b60405161005091906100d9565b60405180910390f35b610073600480360381019061006e919061009d565b61007e565b005b60008054905090565b8060008190555050565b60008135905061009781610103565b92915050565b6000602082840312156100b3576100b26100fe565b5b60006100c184828501610088565b91505092915050565b6100d3816100f4565b82525050565b60006020820190506100ee60008301846100ca565b92915050565b6000819050919050565b600080fd5b61010c816100f4565b811461011757600080fd5b5056fea26469706673582212209a159a4f3847890f10bfb87871a61eba91c5dbf5ee3cf6398207e292eee22a1664736f6c63430008070033" + } + } + ]} + end) + + lowercase_result = Geth.fetch_internal_transactions([transaction_params], json_rpc_named_arguments) + + assert uppercase_result == lowercase_result + end end describe "fetch_block_internal_transactions/1" do diff --git a/apps/ethereum_jsonrpc/test/ethereum_jsonrpc/receipts_test.exs b/apps/ethereum_jsonrpc/test/ethereum_jsonrpc/receipts_test.exs index f554c31ce572..945f3f78bc94 100644 --- a/apps/ethereum_jsonrpc/test/ethereum_jsonrpc/receipts_test.exs +++ b/apps/ethereum_jsonrpc/test/ethereum_jsonrpc/receipts_test.exs @@ -23,7 +23,6 @@ defmodule EthereumJSONRPC.ReceiptsTest do index: index, first_topic: first_topic, status: status, - type: type, transaction_hash: transaction_hash, transaction_index: transaction_index } = @@ -41,7 +40,6 @@ defmodule EthereumJSONRPC.ReceiptsTest do first_topic: "0xf6db2bace4ac8277384553ad9603d045220a91fb2448ab6130d7a6f044f9a8cf", gas_used: 106_025, status: nil, - type: nil, transaction_hash: "0xd3efddbbeb6ad8d8bb3f6b8c8fb6165567e9dd868013146bdbeb60953c82822a", transaction_index: 17 } @@ -58,7 +56,6 @@ defmodule EthereumJSONRPC.ReceiptsTest do index: 0, first_topic: "0x600bcf04a13e752d1e3670a5a9f1c21177ca2a93c6f5391d4f1298d098097c22", status: :ok, - type: "mined", transaction_hash: "0x53bd884872de3e488692881baeec262e7b95234d3965248c39fe992fffd433e5", transaction_index: 0 } @@ -89,8 +86,7 @@ defmodule EthereumJSONRPC.ReceiptsTest do "data" => data, "logIndex" => integer_to_quantity(index), "topics" => [first_topic], - "transactionHash" => transaction_hash, - "type" => type + "transactionHash" => transaction_hash } ], "status" => native_status, diff --git a/apps/ethereum_jsonrpc/test/ethereum_jsonrpc/request_coordinator_test.exs b/apps/ethereum_jsonrpc/test/ethereum_jsonrpc/request_coordinator_test.exs index efda05ab86e8..0eaa6c1f3159 100644 --- a/apps/ethereum_jsonrpc/test/ethereum_jsonrpc/request_coordinator_test.exs +++ b/apps/ethereum_jsonrpc/test/ethereum_jsonrpc/request_coordinator_test.exs @@ -26,28 +26,35 @@ defmodule EthereumJSONRPC.RequestCoordinatorTest do describe "perform/4" do test "forwards result whenever a request doesn't timeout", %{timeout_table: timeout_table} do expect(EthereumJSONRPC.Mox, :json_rpc, fn _, _ -> {:ok, %{}} end) - assert RollingWindow.count(timeout_table, :throttleable_error_count) == 0 - assert {:ok, %{}} == RequestCoordinator.perform(%{}, EthereumJSONRPC.Mox, [], :timer.minutes(60)) - assert RollingWindow.count(timeout_table, :throttleable_error_count) == 0 + assert RollingWindow.count(timeout_table, :throttleable_error_count_eth_call) == 0 + + assert {:ok, %{}} == + RequestCoordinator.perform(%{method: "eth_call"}, EthereumJSONRPC.Mox, [], :timer.minutes(60)) + + assert RollingWindow.count(timeout_table, :throttleable_error_count_eth_call) == 0 end test "increments counter on certain errors", %{timeout_table: timeout_table} do - expect(EthereumJSONRPC.Mox, :json_rpc, fn :timeout, _ -> {:error, :timeout} end) - expect(EthereumJSONRPC.Mox, :json_rpc, fn :bad_gateway, _ -> {:error, {:bad_gateway, "message"}} end) + expect(EthereumJSONRPC.Mox, :json_rpc, fn %{method: "timeout"}, _ -> {:error, :timeout} end) + expect(EthereumJSONRPC.Mox, :json_rpc, fn %{method: "bad_gateway"}, _ -> {:error, {:bad_gateway, "message"}} end) + + assert {:error, :timeout} == + RequestCoordinator.perform(%{method: "timeout"}, EthereumJSONRPC.Mox, [], :timer.minutes(60)) - assert {:error, :timeout} == RequestCoordinator.perform(:timeout, EthereumJSONRPC.Mox, [], :timer.minutes(60)) - assert RollingWindow.count(timeout_table, :throttleable_error_count) == 1 + assert RollingWindow.count(timeout_table, :throttleable_error_count_timeout) == 1 + assert RollingWindow.count(timeout_table, :throttleable_error_count_bad_gateway) == 0 assert {:error, {:bad_gateway, "message"}} == - RequestCoordinator.perform(:bad_gateway, EthereumJSONRPC.Mox, [], :timer.minutes(60)) + RequestCoordinator.perform(%{method: "bad_gateway"}, EthereumJSONRPC.Mox, [], :timer.minutes(60)) - assert RollingWindow.count(timeout_table, :throttleable_error_count) == 2 + assert RollingWindow.count(timeout_table, :throttleable_error_count_timeout) == 1 + assert RollingWindow.count(timeout_table, :throttleable_error_count_bad_gateway) == 1 end test "returns timeout error if sleep time will exceed max timeout", %{timeout_table: timeout_table} do expect(EthereumJSONRPC.Mox, :json_rpc, 0, fn _, _ -> :ok end) - RollingWindow.inc(timeout_table, :throttleable_error_count) - assert {:error, :timeout} == RequestCoordinator.perform(%{}, EthereumJSONRPC.Mox, [], 1) + RollingWindow.inc(timeout_table, :throttleable_error_count_eth_call) + assert {:error, :timeout} == RequestCoordinator.perform(%{method: "eth_call"}, EthereumJSONRPC.Mox, [], 1) end test "increments throttle_table even when not an error", %{throttle_table: throttle_table} do diff --git a/apps/ethereum_jsonrpc/test/support/ethereum_jsonrpc/case/geth/mox.ex b/apps/ethereum_jsonrpc/test/support/ethereum_jsonrpc/case/geth/mox.ex index d1f113223d68..41a2593cb946 100644 --- a/apps/ethereum_jsonrpc/test/support/ethereum_jsonrpc/case/geth/mox.ex +++ b/apps/ethereum_jsonrpc/test/support/ethereum_jsonrpc/case/geth/mox.ex @@ -6,7 +6,11 @@ defmodule EthereumJSONRPC.Case.Geth.Mox do def setup do %{ block_interval: 500, - json_rpc_named_arguments: [transport: EthereumJSONRPC.Mox, transport_options: [], variant: EthereumJSONRPC.Geth], + json_rpc_named_arguments: [ + transport: EthereumJSONRPC.Mox, + transport_options: [http_options: [timeout: 60000, recv_timeout: 60000]], + variant: EthereumJSONRPC.Geth + ], subscribe_named_arguments: [transport: EthereumJSONRPC.Mox, transport_options: []] } end diff --git a/apps/explorer/config/config.exs b/apps/explorer/config/config.exs index e9d1eb627e9c..eafec8dec772 100644 --- a/apps/explorer/config/config.exs +++ b/apps/explorer/config/config.exs @@ -101,7 +101,7 @@ config :explorer, Explorer.Counters.AddressTokenTransfersCounter, enabled: true, enable_consolidation: true -config :explorer, Explorer.Counters.BlockBurnedFeeCounter, +config :explorer, Explorer.Counters.BlockBurntFeeCounter, enabled: true, enable_consolidation: true @@ -109,10 +109,12 @@ config :explorer, Explorer.Counters.BlockPriorityFeeCounter, enabled: true, enable_consolidation: true -config :explorer, Explorer.TokenTransferTokenIdMigration.Supervisor, enabled: true - config :explorer, Explorer.TokenInstanceOwnerAddressMigration.Supervisor, enabled: true +config :explorer, Explorer.Migrator.TransactionsDenormalization, enabled: true +config :explorer, Explorer.Migrator.AddressCurrentTokenBalanceTokenType, enabled: true +config :explorer, Explorer.Migrator.AddressTokenBalanceTokenType, enabled: true + config :explorer, Explorer.Chain.Fetcher.CheckBytecodeMatchingOnDemand, enabled: true config :explorer, Explorer.Chain.Fetcher.FetchValidatorInfoOnDemand, enabled: true diff --git a/apps/explorer/config/dev/arbitrum.exs b/apps/explorer/config/dev/arbitrum.exs index 1f7cd963e91e..dafcd640fe4c 100644 --- a/apps/explorer/config/dev/arbitrum.exs +++ b/apps/explorer/config/dev/arbitrum.exs @@ -14,6 +14,9 @@ config :explorer, http: EthereumJSONRPC.HTTP.HTTPoison, url: System.get_env("ETHEREUM_JSONRPC_HTTP_URL") || "http://localhost:8545", fallback_url: System.get_env("ETHEREUM_JSONRPC_FALLBACK_HTTP_URL"), + method_to_url: [ + eth_call: ConfigHelper.eth_call_url("http://localhost:8545") + ], http_options: [recv_timeout: timeout, timeout: timeout, hackney: hackney_opts] ], variant: EthereumJSONRPC.Arbitrum diff --git a/apps/explorer/config/dev/besu.exs b/apps/explorer/config/dev/besu.exs index 64db4d5b8180..22c0382163e3 100644 --- a/apps/explorer/config/dev/besu.exs +++ b/apps/explorer/config/dev/besu.exs @@ -16,7 +16,7 @@ config :explorer, fallback_url: System.get_env("ETHEREUM_JSONRPC_FALLBACK_HTTP_URL"), fallback_trace_url: System.get_env("ETHEREUM_JSONRPC_FALLBACK_TRACE_URL"), method_to_url: [ - eth_call: System.get_env("ETHEREUM_JSONRPC_TRACE_URL") || "http://localhost:8545", + eth_call: ConfigHelper.eth_call_url("http://localhost:8545"), eth_getBalance: System.get_env("ETHEREUM_JSONRPC_TRACE_URL") || "http://localhost:8545", trace_replayTransaction: System.get_env("ETHEREUM_JSONRPC_TRACE_URL") || "http://localhost:8545" ], diff --git a/apps/explorer/config/dev/erigon.exs b/apps/explorer/config/dev/erigon.exs index 9253f8ea3081..163f526996f6 100644 --- a/apps/explorer/config/dev/erigon.exs +++ b/apps/explorer/config/dev/erigon.exs @@ -16,7 +16,7 @@ config :explorer, fallback_url: System.get_env("ETHEREUM_JSONRPC_FALLBACK_HTTP_URL"), fallback_trace_url: System.get_env("ETHEREUM_JSONRPC_FALLBACK_TRACE_URL"), method_to_url: [ - eth_call: System.get_env("ETHEREUM_JSONRPC_TRACE_URL") || "http://localhost:8545", + eth_call: ConfigHelper.eth_call_url("http://localhost:8545"), eth_getBalance: System.get_env("ETHEREUM_JSONRPC_TRACE_URL") || "http://localhost:8545", trace_replayTransaction: System.get_env("ETHEREUM_JSONRPC_TRACE_URL") || "http://localhost:8545" ], diff --git a/apps/explorer/config/dev/ganache.exs b/apps/explorer/config/dev/ganache.exs index 8f399f532ff5..f7ddd7cbe37f 100644 --- a/apps/explorer/config/dev/ganache.exs +++ b/apps/explorer/config/dev/ganache.exs @@ -14,6 +14,9 @@ config :explorer, http: EthereumJSONRPC.HTTP.HTTPoison, url: System.get_env("ETHEREUM_JSONRPC_HTTP_URL") || "http://localhost:7545", fallback_url: System.get_env("ETHEREUM_JSONRPC_FALLBACK_HTTP_URL"), + method_to_url: [ + eth_call: ConfigHelper.eth_call_url("http://localhost:7545") + ], http_options: [recv_timeout: timeout, timeout: timeout, hackney: hackney_opts] ], variant: EthereumJSONRPC.Ganache diff --git a/apps/explorer/config/dev/geth.exs b/apps/explorer/config/dev/geth.exs index 4ac8203aecac..8b644ff987d2 100644 --- a/apps/explorer/config/dev/geth.exs +++ b/apps/explorer/config/dev/geth.exs @@ -16,6 +16,7 @@ config :explorer, fallback_url: System.get_env("ETHEREUM_JSONRPC_FALLBACK_HTTP_URL"), fallback_trace_url: System.get_env("ETHEREUM_JSONRPC_FALLBACK_TRACE_URL"), method_to_url: [ + eth_call: ConfigHelper.eth_call_url("http://localhost:8545"), debug_traceTransaction: System.get_env("ETHEREUM_JSONRPC_TRACE_URL") || "http://localhost:8545" ], http_options: [recv_timeout: timeout, timeout: timeout, hackney: hackney_opts] diff --git a/apps/explorer/config/dev/nethermind.exs b/apps/explorer/config/dev/nethermind.exs index e92b980507f0..2553a16db492 100644 --- a/apps/explorer/config/dev/nethermind.exs +++ b/apps/explorer/config/dev/nethermind.exs @@ -16,7 +16,7 @@ config :explorer, fallback_url: System.get_env("ETHEREUM_JSONRPC_FALLBACK_HTTP_URL"), fallback_trace_url: System.get_env("ETHEREUM_JSONRPC_FALLBACK_TRACE_URL"), method_to_url: [ - eth_call: System.get_env("ETHEREUM_JSONRPC_TRACE_URL") || "http://localhost:8545", + eth_call: ConfigHelper.eth_call_url("http://localhost:8545"), eth_getBalance: System.get_env("ETHEREUM_JSONRPC_TRACE_URL") || "http://localhost:8545", trace_replayTransaction: System.get_env("ETHEREUM_JSONRPC_TRACE_URL") || "http://localhost:8545" ], diff --git a/apps/explorer/config/dev/rsk.exs b/apps/explorer/config/dev/rsk.exs index 597d78c395a0..699584ea0324 100644 --- a/apps/explorer/config/dev/rsk.exs +++ b/apps/explorer/config/dev/rsk.exs @@ -16,7 +16,7 @@ config :explorer, fallback_url: System.get_env("ETHEREUM_JSONRPC_FALLBACK_HTTP_URL"), fallback_trace_url: System.get_env("ETHEREUM_JSONRPC_FALLBACK_TRACE_URL"), method_to_url: [ - eth_call: System.get_env("ETHEREUM_JSONRPC_TRACE_URL") || "http://localhost:8545", + eth_call: ConfigHelper.eth_call_url("http://localhost:8545"), eth_getBalance: System.get_env("ETHEREUM_JSONRPC_TRACE_URL") || "http://localhost:8545", trace_replayTransaction: System.get_env("ETHEREUM_JSONRPC_TRACE_URL") || "http://localhost:8545" ], diff --git a/apps/explorer/config/prod/arbitrum.exs b/apps/explorer/config/prod/arbitrum.exs index ea4af81646f2..5f45a1a071db 100644 --- a/apps/explorer/config/prod/arbitrum.exs +++ b/apps/explorer/config/prod/arbitrum.exs @@ -14,6 +14,9 @@ config :explorer, http: EthereumJSONRPC.HTTP.HTTPoison, url: System.get_env("ETHEREUM_JSONRPC_HTTP_URL"), fallback_url: System.get_env("ETHEREUM_JSONRPC_FALLBACK_HTTP_URL"), + method_to_url: [ + eth_call: ConfigHelper.eth_call_url() + ], http_options: [recv_timeout: timeout, timeout: timeout, hackney: hackney_opts] ], variant: EthereumJSONRPC.Arbitrum diff --git a/apps/explorer/config/prod/besu.exs b/apps/explorer/config/prod/besu.exs index 26df9c5bd480..a486b5c6dcda 100644 --- a/apps/explorer/config/prod/besu.exs +++ b/apps/explorer/config/prod/besu.exs @@ -16,7 +16,7 @@ config :explorer, fallback_url: System.get_env("ETHEREUM_JSONRPC_FALLBACK_HTTP_URL"), fallback_trace_url: System.get_env("ETHEREUM_JSONRPC_FALLBACK_TRACE_URL"), method_to_url: [ - eth_call: System.get_env("ETHEREUM_JSONRPC_TRACE_URL"), + eth_call: ConfigHelper.eth_call_url(), eth_getBalance: System.get_env("ETHEREUM_JSONRPC_TRACE_URL"), trace_replayTransaction: System.get_env("ETHEREUM_JSONRPC_TRACE_URL") ], diff --git a/apps/explorer/config/prod/erigon.exs b/apps/explorer/config/prod/erigon.exs index 0a954a88b1df..1ca954c1ef41 100644 --- a/apps/explorer/config/prod/erigon.exs +++ b/apps/explorer/config/prod/erigon.exs @@ -16,7 +16,7 @@ config :explorer, fallback_url: System.get_env("ETHEREUM_JSONRPC_FALLBACK_HTTP_URL"), fallback_trace_url: System.get_env("ETHEREUM_JSONRPC_FALLBACK_TRACE_URL"), method_to_url: [ - eth_call: System.get_env("ETHEREUM_JSONRPC_TRACE_URL"), + eth_call: ConfigHelper.eth_call_url(), eth_getBalance: System.get_env("ETHEREUM_JSONRPC_TRACE_URL"), trace_replayTransaction: System.get_env("ETHEREUM_JSONRPC_TRACE_URL") ], diff --git a/apps/explorer/config/prod/ganache.exs b/apps/explorer/config/prod/ganache.exs index 25de1e5b9a31..0956e4133c06 100644 --- a/apps/explorer/config/prod/ganache.exs +++ b/apps/explorer/config/prod/ganache.exs @@ -14,6 +14,9 @@ config :explorer, http: EthereumJSONRPC.HTTP.HTTPoison, url: System.get_env("ETHEREUM_JSONRPC_HTTP_URL"), fallback_url: System.get_env("ETHEREUM_JSONRPC_FALLBACK_HTTP_URL"), + method_to_url: [ + eth_call: ConfigHelper.eth_call_url() + ], http_options: [recv_timeout: timeout, timeout: timeout, hackney: hackney_opts] ], variant: EthereumJSONRPC.Ganache diff --git a/apps/explorer/config/prod/geth.exs b/apps/explorer/config/prod/geth.exs index 3a81acee9c00..46d1f6bc110b 100644 --- a/apps/explorer/config/prod/geth.exs +++ b/apps/explorer/config/prod/geth.exs @@ -16,6 +16,7 @@ config :explorer, fallback_url: System.get_env("ETHEREUM_JSONRPC_FALLBACK_HTTP_URL"), fallback_trace_url: System.get_env("ETHEREUM_JSONRPC_FALLBACK_TRACE_URL"), method_to_url: [ + eth_call: ConfigHelper.eth_call_url(), debug_traceTransaction: System.get_env("ETHEREUM_JSONRPC_TRACE_URL") ], http_options: [recv_timeout: timeout, timeout: timeout, hackney: hackney_opts] diff --git a/apps/explorer/config/prod/nethermind.exs b/apps/explorer/config/prod/nethermind.exs index 4057a0d99d03..bf5a0440865e 100644 --- a/apps/explorer/config/prod/nethermind.exs +++ b/apps/explorer/config/prod/nethermind.exs @@ -16,7 +16,7 @@ config :explorer, fallback_url: System.get_env("ETHEREUM_JSONRPC_FALLBACK_HTTP_URL"), fallback_trace_url: System.get_env("ETHEREUM_JSONRPC_FALLBACK_TRACE_URL"), method_to_url: [ - eth_call: System.get_env("ETHEREUM_JSONRPC_TRACE_URL"), + eth_call: ConfigHelper.eth_call_url(), eth_getBalance: System.get_env("ETHEREUM_JSONRPC_TRACE_URL"), trace_replayTransaction: System.get_env("ETHEREUM_JSONRPC_TRACE_URL") ], diff --git a/apps/explorer/config/prod/rsk.exs b/apps/explorer/config/prod/rsk.exs index 9f65d8be864f..35120eec2c71 100644 --- a/apps/explorer/config/prod/rsk.exs +++ b/apps/explorer/config/prod/rsk.exs @@ -16,7 +16,7 @@ config :explorer, fallback_url: System.get_env("ETHEREUM_JSONRPC_FALLBACK_HTTP_URL"), fallback_trace_url: System.get_env("ETHEREUM_JSONRPC_FALLBACK_TRACE_URL"), method_to_url: [ - eth_call: System.get_env("ETHEREUM_JSONRPC_TRACE_URL"), + eth_call: ConfigHelper.eth_call_url(), eth_getBalance: System.get_env("ETHEREUM_JSONRPC_TRACE_URL"), trace_replayTransaction: System.get_env("ETHEREUM_JSONRPC_TRACE_URL") ], diff --git a/apps/explorer/config/runtime/test.exs b/apps/explorer/config/runtime/test.exs index 9449463a5b9b..ec346c1d3f89 100644 --- a/apps/explorer/config/runtime/test.exs +++ b/apps/explorer/config/runtime/test.exs @@ -33,10 +33,12 @@ config :explorer, Explorer.Market.History.Cataloger, enabled: false config :explorer, Explorer.Tracer, disabled?: false -config :explorer, Explorer.TokenTransferTokenIdMigration.Supervisor, enabled: false - config :explorer, Explorer.TokenInstanceOwnerAddressMigration.Supervisor, enabled: false +config :explorer, Explorer.Migrator.TransactionsDenormalization, enabled: false +config :explorer, Explorer.Migrator.AddressCurrentTokenBalanceTokenType, enabled: false +config :explorer, Explorer.Migrator.AddressTokenBalanceTokenType, enabled: false + config :explorer, realtime_events_sender: Explorer.Chain.Events.SimpleSender diff --git a/apps/explorer/lib/explorer/account/notifier/forbidden_address.ex b/apps/explorer/lib/explorer/account/notifier/forbidden_address.ex index 6022c87fcc7d..b20cd898ed2a 100644 --- a/apps/explorer/lib/explorer/account/notifier/forbidden_address.ex +++ b/apps/explorer/lib/explorer/account/notifier/forbidden_address.ex @@ -5,16 +5,16 @@ defmodule Explorer.Account.Notifier.ForbiddenAddress do import Explorer.Chain.SmartContract, only: [burn_address_hash_string: 0] + alias Explorer.Chain.Address + @blacklist [ burn_address_hash_string(), "0x000000000000000000000000000000000000dEaD" ] - alias Explorer.{AccessHelper, Repo} - alias Explorer.Chain.Token + alias Explorer.AccessHelper - import Ecto.Query, only: [from: 2] - import Explorer.Chain, only: [string_to_address_hash: 1] + import Explorer.Chain, only: [string_to_address_hash: 1, hash_to_address: 1] def check(address_string) when is_bitstring(address_string) do case format_address(address_string) do @@ -32,7 +32,7 @@ defmodule Explorer.Account.Notifier.ForbiddenAddress do {:error, "This address is blacklisted"} is_contract(address_hash) -> - {:error, "This address isn't personal"} + {:error, "This address isn't EOA"} match?({:restricted_access, true}, AccessHelper.restricted_access?(to_string(address_hash), %{})) -> {:error, "This address has restricted access"} @@ -43,14 +43,10 @@ defmodule Explorer.Account.Notifier.ForbiddenAddress do end defp is_contract(%Explorer.Chain.Hash{} = address_hash) do - query = - from( - token in Token, - where: token.contract_address_hash == ^address_hash - ) - - contract_addresses = Repo.all(query) - List.first(contract_addresses) + case hash_to_address(address_hash) do + {:error, :not_found} -> false + {:ok, address} -> Address.is_smart_contract(address) + end end defp format_address(address_hash_string) do diff --git a/apps/explorer/lib/explorer/account/notifier/notify.ex b/apps/explorer/lib/explorer/account/notifier/notify.ex index 70e633be8da8..f9c9232546ac 100644 --- a/apps/explorer/lib/explorer/account/notifier/notify.ex +++ b/apps/explorer/lib/explorer/account/notifier/notify.ex @@ -55,7 +55,8 @@ defmodule Explorer.Account.Notifier.Notify do defp notify_watchlists(nil), do: nil defp notify_watchlist(%WatchlistAddress{} = address, summary, direction) do - case ForbiddenAddress.check(address.address_hash) do + case !WatchlistNotification.limit_reached_for_watchlist_id?(address.watchlist_id) && + ForbiddenAddress.check(address.address_hash) do {:ok, _address_hash} -> with %WatchlistNotification{} = notification <- build_watchlist_notification( @@ -74,6 +75,9 @@ defmodule Explorer.Account.Notifier.Notify do {:error, _message} -> nil + + false -> + nil end end @@ -106,9 +110,6 @@ defmodule Explorer.Account.Notifier.Notify do Logger.info("--- email delivery response: FAILED", fetcher: :account) Logger.info(error, fetcher: :account) end - else - Logger.info("--- email delivery response: FAILED", fetcher: :account) - Logger.info("Email is not composed (is nil)", fetcher: :account) end end @@ -119,6 +120,7 @@ defmodule Explorer.Account.Notifier.Notify do if is_watched(address, summary, direction) do %WatchlistNotification{ watchlist_address_id: address.id, + watchlist_id: address.watchlist_id, transaction_hash: summary.transaction_hash, from_address_hash: summary.from_address_hash, to_address_hash: summary.to_address_hash, diff --git a/apps/explorer/lib/explorer/account/watchlist_notification.ex b/apps/explorer/lib/explorer/account/watchlist_notification.ex index 935e53218780..cc45561073ef 100644 --- a/apps/explorer/lib/explorer/account/watchlist_notification.ex +++ b/apps/explorer/lib/explorer/account/watchlist_notification.ex @@ -1,6 +1,6 @@ defmodule Explorer.Account.WatchlistNotification do @moduledoc """ - Stored notification about event + Stored notification about event related to WatchlistAddress """ @@ -9,7 +9,8 @@ defmodule Explorer.Account.WatchlistNotification do import Ecto.Changeset import Explorer.Chain, only: [hash_to_lower_case_string: 1] - alias Explorer.Account.WatchlistAddress + alias Explorer.Repo + alias Explorer.Account.{Watchlist, WatchlistAddress} schema "account_watchlist_notifications" do field(:amount, :decimal) @@ -24,6 +25,7 @@ defmodule Explorer.Account.WatchlistNotification do field(:subject_hash, Cloak.Ecto.SHA256) belongs_to(:watchlist_address, WatchlistAddress) + belongs_to(:watchlist, Watchlist) field(:from_address_hash, Explorer.Encrypted.AddressHash) field(:to_address_hash, Explorer.Encrypted.AddressHash) @@ -62,4 +64,23 @@ defmodule Explorer.Account.WatchlistNotification do |> put_change(:transaction_hash_hash, hash_to_lower_case_string(get_field(changeset, :transaction_hash))) |> put_change(:subject_hash, get_field(changeset, :subject)) end + + @doc """ + Check if amount of watchlist notifications for the last 30 days is less than ACCOUNT_WATCHLIST_NOTIFICATIONS_LIMIT_FOR_30_DAYS + """ + @spec limit_reached_for_watchlist_id?(integer) :: boolean + def limit_reached_for_watchlist_id?(watchlist_id) do + __MODULE__ + |> where( + [wn], + wn.watchlist_id == ^watchlist_id and + fragment("NOW() - ? at time zone 'UTC' <= interval '30 days'", wn.inserted_at) + ) + |> limit(^watchlist_notification_30_days_limit()) + |> Repo.account_repo().aggregate(:count) == watchlist_notification_30_days_limit() + end + + defp watchlist_notification_30_days_limit do + Application.get_env(:explorer, Explorer.Account)[:notifications_limit_for_30_days] + end end diff --git a/apps/explorer/lib/explorer/application.ex b/apps/explorer/lib/explorer/application.ex index f59e56903bc5..52b196489d07 100644 --- a/apps/explorer/lib/explorer/application.ex +++ b/apps/explorer/lib/explorer/application.ex @@ -5,13 +5,14 @@ defmodule Explorer.Application do use Application - alias Explorer.{Admin, TokenTransferTokenIdMigration} + alias Explorer.Admin alias Explorer.Chain.Cache.{ Accounts, AddressesTabsCounters, AddressSum, AddressSumMinusBurnt, + BackgroundMigrations, Block, BlockNumber, Blocks, @@ -63,6 +64,7 @@ defmodule Explorer.Application do Accounts, AddressSum, AddressSumMinusBurnt, + BackgroundMigrations, Block, BlockNumber, Blocks, @@ -113,19 +115,21 @@ defmodule Explorer.Application do configure(Explorer.Counters.AddressTokenUsdSum), configure(Explorer.Counters.TokenHoldersCounter), configure(Explorer.Counters.TokenTransfersCounter), - configure(Explorer.Counters.BlockBurnedFeeCounter), + configure(Explorer.Counters.BlockBurntFeeCounter), configure(Explorer.Counters.BlockPriorityFeeCounter), configure(Explorer.Counters.AverageBlockTime), configure(Explorer.Counters.Bridge), configure(Explorer.Validator.MetadataProcessor), configure(Explorer.Tags.AddressTag.Cataloger), configure(MinMissingBlockNumber), - configure(TokenTransferTokenIdMigration.Supervisor), configure(Explorer.Chain.Fetcher.CheckBytecodeMatchingOnDemand), configure(Explorer.Chain.Fetcher.FetchValidatorInfoOnDemand), configure(Explorer.TokenInstanceOwnerAddressMigration.Supervisor), sc_microservice_configure(Explorer.Chain.Fetcher.LookUpSmartContractSourcesOnDemand), - configure(Explorer.Chain.Cache.RootstockLockedBTC) + configure(Explorer.Chain.Cache.RootstockLockedBTC), + configure(Explorer.Migrator.TransactionsDenormalization), + configure(Explorer.Migrator.AddressCurrentTokenBalanceTokenType), + configure(Explorer.Migrator.AddressTokenBalanceTokenType) ] |> List.flatten() diff --git a/apps/explorer/lib/explorer/chain.ex b/apps/explorer/lib/explorer/chain.ex index 63d5935a3e6f..e61f9cf6ca22 100644 --- a/apps/explorer/lib/explorer/chain.ex +++ b/apps/explorer/lib/explorer/chain.ex @@ -33,6 +33,7 @@ defmodule Explorer.Chain do alias Ecto.{Changeset, Multi} alias EthereumJSONRPC.Transaction, as: EthereumJSONRPCTransaction + alias EthereumJSONRPC.Utility.RangesHelper alias Explorer.Account.WatchlistAddress @@ -50,12 +51,12 @@ defmodule Explorer.Chain do CurrencyHelper, Data, DecompiledSmartContract, + DenormalizationHelper, Hash, Import, InternalTransaction, Log, PendingBlockOperation, - Search, SmartContract, Token, Token.Instance, @@ -160,7 +161,7 @@ defmodule Explorer.Chain do """ @type necessity_by_association :: %{association => necessity} - @typep necessity_by_association_option :: {:necessity_by_association, necessity_by_association} + @type necessity_by_association_option :: {:necessity_by_association, necessity_by_association} @type paging_options :: {:paging_options, PagingOptions.t()} @typep balance_by_day :: %{date: String.t(), value: Wei.t()} @type api? :: {:api?, true | false} @@ -202,7 +203,7 @@ defmodule Explorer.Chain do InternalTransaction |> InternalTransaction.where_nonpending_block() |> InternalTransaction.where_address_fields_match(hash, :to_address_hash) - |> InternalTransaction.where_block_number_in_period(from_block, to_block) + |> where_block_number_in_period(from_block, to_block) |> common_where_limit_order(paging_options) |> wrapped_union_subquery() @@ -210,7 +211,7 @@ defmodule Explorer.Chain do InternalTransaction |> InternalTransaction.where_nonpending_block() |> InternalTransaction.where_address_fields_match(hash, :from_address_hash) - |> InternalTransaction.where_block_number_in_period(from_block, to_block) + |> where_block_number_in_period(from_block, to_block) |> common_where_limit_order(paging_options) |> wrapped_union_subquery() @@ -218,7 +219,7 @@ defmodule Explorer.Chain do InternalTransaction |> InternalTransaction.where_nonpending_block() |> InternalTransaction.where_address_fields_match(hash, :created_contract_address_hash) - |> InternalTransaction.where_block_number_in_period(from_block, to_block) + |> where_block_number_in_period(from_block, to_block) |> common_where_limit_order(paging_options) |> wrapped_union_subquery() @@ -234,7 +235,7 @@ defmodule Explorer.Chain do InternalTransaction |> InternalTransaction.where_nonpending_block() |> InternalTransaction.where_address_fields_match(hash, direction) - |> InternalTransaction.where_block_number_in_period(from_block, to_block) + |> where_block_number_in_period(from_block, to_block) |> common_where_limit_order(paging_options) |> preload(:block) |> join_associations(necessity_by_association) @@ -262,234 +263,30 @@ defmodule Explorer.Chain do ) end - @doc """ - Fetches the transactions related to the address with the given hash, including - transactions that only have the address in the `token_transfers` related table - and rewards for block validation. - - This query is divided into multiple subqueries intentionally in order to - improve the listing performance. - - The `token_transfers` table tends to grow exponentially, and the query results - with a `transactions` `join` statement takes too long. - - To solve this the `transaction_hashes` are fetched in a separate query, and - paginated through the `block_number` already present in the `token_transfers` - table. - - ## Options - - * `:necessity_by_association` - use to load `t:association/0` as `:required` or `:optional`. If an association is - `:required`, and the `t:Explorer.Chain.Transaction.t/0` has no associated record for that association, then the - `t:Explorer.Chain.Transaction.t/0` will not be included in the page `entries`. - * `:paging_options` - a `t:Explorer.PagingOptions.t/0` used to specify the `:page_size` and - `:key` (a tuple of the lowest/oldest `{block_number, index}`) and. Results will be the transactions older than - the `block_number` and `index` that are passed. - - """ - @spec address_to_transactions_with_rewards(Hash.Address.t(), [paging_options | necessity_by_association_option]) :: - [ - Transaction.t() - ] - def address_to_transactions_with_rewards(address_hash, options \\ []) when is_list(options) do - paging_options = Keyword.get(options, :paging_options, @default_paging_options) - - if Application.get_env(:block_scout_web, BlockScoutWeb.Chain)[:has_emission_funds] do - cond do - Keyword.get(options, :direction) == :from -> - address_to_transactions_without_rewards(address_hash, options) - - address_has_rewards?(address_hash) -> - address_with_rewards(address_hash, options, paging_options) - - true -> - address_to_transactions_without_rewards(address_hash, options) - end - else - address_to_transactions_without_rewards(address_hash, options) - end - end - - defp address_with_rewards(address_hash, options, paging_options) do - %{payout_key: block_miner_payout_address} = Reward.get_validator_payout_key_by_mining_from_db(address_hash, options) - - if block_miner_payout_address && address_hash == block_miner_payout_address do - transactions_with_rewards_results(address_hash, options, paging_options) - else - address_to_transactions_without_rewards(address_hash, options) - end - end - - defp transactions_with_rewards_results(address_hash, options, paging_options) do - blocks_range = address_to_transactions_tasks_range_of_blocks(address_hash, options) - - rewards_task = - Task.async(fn -> Reward.fetch_emission_rewards_tuples(address_hash, paging_options, blocks_range, options) end) - - [rewards_task | address_to_transactions_tasks(address_hash, options, true)] - |> wait_for_address_transactions() - |> Enum.sort_by(fn item -> - case item do - {%Reward{} = emission_reward, _} -> - {-emission_reward.block.number, 1} - - item -> - process_item(item) - end - end) - |> Enum.dedup_by(fn item -> - case item do - {%Reward{} = emission_reward, _} -> - {emission_reward.block_hash, emission_reward.address_hash, emission_reward.address_type} - - transaction -> - transaction.hash - end - end) - |> Enum.take(paging_options.page_size) - end - - defp process_item(item) do - block_number = if item.block_number, do: -item.block_number, else: 0 - index = if item.index, do: -item.index, else: 0 - {block_number, index} - end - - def address_to_transactions_without_rewards(address_hash, options, old_ui? \\ true) do - paging_options = Keyword.get(options, :paging_options, @default_paging_options) - - address_hash - |> address_to_transactions_tasks(options, old_ui?) - |> wait_for_address_transactions() - |> Enum.sort_by(&{&1.block_number, &1.index}, &>=/2) - |> Enum.dedup_by(& &1.hash) - |> Enum.take(paging_options.page_size) - end - def address_hashes_to_mined_transactions_without_rewards(address_hashes, options) do paging_options = Keyword.get(options, :paging_options, @default_paging_options) address_hashes |> address_hashes_to_mined_transactions_tasks(options) - |> wait_for_address_transactions() + |> Transaction.wait_for_address_transactions() |> Enum.sort_by(&{&1.block_number, &1.index}, &>=/2) |> Enum.dedup_by(& &1.hash) |> Enum.take(paging_options.page_size) end - defp address_to_transactions_tasks_query(options, only_mined? \\ false) do - from_block = from_block(options) - to_block = to_block(options) - - options - |> Keyword.get(:paging_options, @default_paging_options) - |> fetch_transactions(from_block, to_block, !only_mined?) - end - - defp transactions_block_numbers_at_address(address_hash, options) do - direction = Keyword.get(options, :direction) - - options - |> address_to_transactions_tasks_query() - |> Transaction.not_pending_transactions() - |> select([t], t.block_number) - |> Transaction.matching_address_queries_list(direction, address_hash) - end - - defp address_to_transactions_tasks(address_hash, options, old_ui?) do - direction = Keyword.get(options, :direction) - necessity_by_association = Keyword.get(options, :necessity_by_association, %{}) - - options - |> address_to_transactions_tasks_query() - |> Transaction.not_dropped_or_replaced_transactions() - |> join_associations(necessity_by_association) - |> put_has_token_transfers_to_tx(old_ui?) - |> Transaction.matching_address_queries_list(direction, address_hash) - |> Enum.map(fn query -> Task.async(fn -> select_repo(options).all(query) end) end) - end - defp address_hashes_to_mined_transactions_tasks(address_hashes, options) do direction = Keyword.get(options, :direction) necessity_by_association = Keyword.get(options, :necessity_by_association, %{}) options - |> address_to_transactions_tasks_query(true) + |> Transaction.address_to_transactions_tasks_query(true) |> Transaction.not_pending_transactions() |> join_associations(necessity_by_association) - |> put_has_token_transfers_to_tx(false) + |> Transaction.put_has_token_transfers_to_tx(false) |> Transaction.matching_address_queries_list(direction, address_hashes) |> Enum.map(fn query -> Task.async(fn -> select_repo(options).all(query) end) end) end - def address_to_transactions_tasks_range_of_blocks(address_hash, options) do - extremums_list = - address_hash - |> transactions_block_numbers_at_address(options) - |> Enum.map(fn query -> - extremum_query = - from( - q in subquery(query), - select: %{min_block_number: min(q.block_number), max_block_number: max(q.block_number)} - ) - - extremum_query - |> Repo.one!() - end) - - extremums_list - |> Enum.reduce(%{min_block_number: nil, max_block_number: 0}, fn %{ - min_block_number: min_number, - max_block_number: max_number - }, - extremums_result -> - current_min_number = Map.get(extremums_result, :min_block_number) - current_max_number = Map.get(extremums_result, :max_block_number) - - extremums_result - |> process_extremums_result_against_min_number(current_min_number, min_number) - |> process_extremums_result_against_max_number(current_max_number, max_number) - end) - end - - defp process_extremums_result_against_min_number(extremums_result, current_min_number, min_number) - when is_number(current_min_number) and - not (is_number(min_number) and min_number > 0 and min_number < current_min_number) do - extremums_result - end - - defp process_extremums_result_against_min_number(extremums_result, _current_min_number, min_number) do - extremums_result - |> Map.put(:min_block_number, min_number) - end - - defp process_extremums_result_against_max_number(extremums_result, current_max_number, max_number) - when is_number(max_number) and max_number > 0 and max_number > current_max_number do - extremums_result - |> Map.put(:max_block_number, max_number) - end - - defp process_extremums_result_against_max_number(extremums_result, _current_max_number, _max_number) do - extremums_result - end - - defp wait_for_address_transactions(tasks) do - tasks - |> Task.yield_many(:timer.seconds(20)) - |> Enum.flat_map(fn {_task, res} -> - case res do - {:ok, result} -> - result - - {:exit, reason} -> - raise "Query fetching address transactions terminated: #{inspect(reason)}" - - nil -> - raise "Query fetching address transactions timed out." - end - end) - end - @spec address_hash_to_token_transfers(Hash.Address.t(), Keyword.t()) :: [Transaction.t()] def address_hash_to_token_transfers(address_hash, options \\ []) do paging_options = Keyword.get(options, :paging_options, @default_paging_options) @@ -498,7 +295,7 @@ defmodule Explorer.Chain do direction |> Transaction.transactions_with_token_transfers_direction(address_hash) |> Transaction.preload_token_transfers(address_hash) - |> handle_paging_options(paging_options) + |> Transaction.handle_paging_options(paging_options) |> Repo.all() end @@ -556,15 +353,26 @@ defmodule Explorer.Chain do to_block = to_block(options) base = - from(log in Log, - order_by: [desc: log.block_number, desc: log.index], - where: log.address_hash == ^address_hash, - limit: ^paging_options.page_size, - select: log, - inner_join: block in Block, - on: block.hash == log.block_hash, - where: block.consensus == true - ) + if DenormalizationHelper.denormalization_finished?() do + from(log in Log, + order_by: [desc: log.block_number, desc: log.index], + where: log.address_hash == ^address_hash, + limit: ^paging_options.page_size, + select: log, + inner_join: transaction in assoc(log, :transaction), + where: transaction.block_consensus == true + ) + else + from(log in Log, + order_by: [desc: log.block_number, desc: log.index], + where: log.address_hash == ^address_hash, + limit: ^paging_options.page_size, + select: log, + inner_join: block in Block, + on: block.hash == log.block_hash, + where: block.consensus == true + ) + end preloaded_query = if csv_export? do @@ -633,7 +441,7 @@ defmodule Explorer.Chain do address_hash |> Transaction.transactions_with_token_transfers(token_hash) |> Transaction.preload_token_transfers(address_hash) - |> handle_paging_options(paging_options) + |> Transaction.handle_paging_options(paging_options) |> Repo.all() end @@ -717,65 +525,6 @@ defmodule Explorer.Chain do end end - def txn_fees(transactions) do - Enum.reduce(transactions, Decimal.new(0), fn %{gas_used: gas_used, gas_price: gas_price}, acc -> - gas_used - |> Decimal.new() - |> Decimal.mult(gas_price_to_decimal(gas_price)) - |> Decimal.add(acc) - end) - end - - defp gas_price_to_decimal(%Wei{} = wei), do: wei.value - defp gas_price_to_decimal(gas_price), do: Decimal.new(gas_price) - - def burned_fees(transactions, base_fee_per_gas) do - burned_fee_counter = - transactions - |> Enum.reduce(Decimal.new(0), fn %{gas_used: gas_used}, acc -> - gas_used - |> Decimal.new() - |> Decimal.add(acc) - end) - - base_fee_per_gas && Wei.mult(base_fee_per_gas_to_wei(base_fee_per_gas), burned_fee_counter) - end - - defp base_fee_per_gas_to_wei(%Wei{} = wei), do: wei - defp base_fee_per_gas_to_wei(base_fee_per_gas), do: %Wei{value: Decimal.new(base_fee_per_gas)} - - @uncle_reward_coef 1 / 32 - def block_reward_by_parts(block, transactions) do - %{hash: block_hash, number: block_number} = block - base_fee_per_gas = Map.get(block, :base_fee_per_gas) - - txn_fees = txn_fees(transactions) - - static_reward = - Repo.one( - from( - er in EmissionReward, - where: fragment("int8range(?, ?) <@ ?", ^block_number, ^(block_number + 1), er.block_range), - select: er.reward - ) - ) || %Wei{value: Decimal.new(0)} - - has_uncles? = is_list(block.uncles) and not Enum.empty?(block.uncles) - - burned_fees = burned_fees(transactions, base_fee_per_gas) - uncle_reward = (has_uncles? && Wei.mult(static_reward, Decimal.from_float(@uncle_reward_coef))) || nil - - %{ - block_number: block_number, - block_hash: block_hash, - miner_hash: block.miner_hash, - static_reward: static_reward, - txn_fees: %Wei{value: txn_fees}, - burned_fees: burned_fees || %Wei{value: Decimal.new(0)}, - uncle_reward: uncle_reward || %Wei{value: Decimal.new(0)} - } - end - @doc """ The `t:Explorer.Chain.Wei.t/0` paid to the miners of the `t:Explorer.Chain.Block.t/0`s with `hash` `Explorer.Chain.Hash.Full.t/0` by the signers of the transactions in those blocks to cover the gas fee @@ -784,17 +533,34 @@ defmodule Explorer.Chain do @spec gas_payment_by_block_hash([Hash.Full.t()]) :: %{Hash.Full.t() => Wei.t()} def gas_payment_by_block_hash(block_hashes) when is_list(block_hashes) do query = - from( - block in Block, - left_join: transaction in assoc(block, :transactions), - where: block.hash in ^block_hashes and block.consensus == true, - group_by: block.hash, - select: {block.hash, %Wei{value: coalesce(sum(transaction.gas_used * transaction.gas_price), 0)}} - ) + if DenormalizationHelper.denormalization_finished?() do + from( + transaction in Transaction, + where: transaction.block_hash in ^block_hashes and transaction.block_consensus == true, + group_by: transaction.block_hash, + select: {transaction.block_hash, %Wei{value: coalesce(sum(transaction.gas_used * transaction.gas_price), 0)}} + ) + else + from( + block in Block, + left_join: transaction in assoc(block, :transactions), + where: block.hash in ^block_hashes and block.consensus == true, + group_by: block.hash, + select: {block.hash, %Wei{value: coalesce(sum(transaction.gas_used * transaction.gas_price), 0)}} + ) + end - query - |> Repo.all() - |> Enum.into(%{}) + initial_gas_payments = + block_hashes + |> Enum.map(&{&1, %Wei{value: Decimal.new(0)}}) + |> Enum.into(%{}) + + existing_data = + query + |> Repo.all() + |> Enum.into(%{}) + + Map.merge(initial_gas_payments, existing_data) end def timestamp_by_block_hash(block_hashes) when is_list(block_hashes) do @@ -836,7 +602,7 @@ defmodule Explorer.Chain do |> join(:inner, [transaction], block in assoc(transaction, :block)) |> where([_, block], block.hash == ^block_hash) |> join_associations(necessity_by_association) - |> put_has_token_transfers_to_tx(old_ui?) + |> Transaction.put_has_token_transfers_to_tx(old_ui?) |> (&if(old_ui?, do: preload(&1, [{:token_transfers, [:token, :from_address, :to_address]}]), else: &1)).() |> select_repo(options).all() |> (&if(old_ui?, @@ -856,7 +622,7 @@ defmodule Explorer.Chain do |> fetch_transactions_in_descending_order_by_block_and_index() |> where(execution_node_hash: ^execution_node_hash) |> join_associations(necessity_by_association) - |> put_has_token_transfers_to_tx(false) + |> Transaction.put_has_token_transfers_to_tx(false) |> (& &1).() |> select_repo(options).all() |> (&Enum.map(&1, fn tx -> preload_token_transfers(tx, @token_transfers_necessity_by_association, options) end)).() @@ -1141,7 +907,9 @@ defmodule Explorer.Chain do {:actual, Decimal.new(4)} """ - @spec fee(Transaction.t(), :ether | :gwei | :wei) :: {:maximum, Decimal.t()} | {:actual, Decimal.t()} + @spec fee(Transaction.t(), :ether | :gwei | :wei) :: {:maximum, Decimal.t()} | {:actual, Decimal.t() | nil} + def fee(%Transaction{gas: _gas, gas_price: nil, gas_used: nil}, _unit), do: {:maximum, nil} + def fee(%Transaction{gas: gas, gas_price: gas_price, gas_used: nil}, unit) do fee = gas_price @@ -1151,6 +919,8 @@ defmodule Explorer.Chain do {:maximum, fee} end + def fee(%Transaction{gas_price: nil, gas_used: _gas_used}, _unit), do: {:actual, nil} + def fee(%Transaction{gas_price: gas_price, gas_used: gas_used}, unit) do fee = gas_price @@ -1277,10 +1047,10 @@ defmodule Explorer.Chain do Optionally it also accepts a boolean to fetch the `has_decompiled_code?` virtual field or not """ - @spec hash_to_address(Hash.Address.t(), [necessity_by_association_option | api?], boolean()) :: + @spec hash_to_address(Hash.Address.t() | binary(), [necessity_by_association_option | api?], boolean()) :: {:ok, Address.t()} | {:error, :not_found} def hash_to_address( - %Hash{byte_count: unquote(Hash.Address.byte_count())} = hash, + hash, options \\ [ necessity_by_association: %{ :contracts_creation_internal_transaction => :optional, @@ -1718,7 +1488,7 @@ defmodule Explorer.Chain do def hashes_to_transactions(hashes, options \\ []) when is_list(hashes) and is_list(options) do necessity_by_association = Keyword.get(options, :necessity_by_association, %{}) - fetch_transactions() + Transaction.fetch_transactions() |> where([transaction], transaction.hash in ^hashes) |> join_associations(necessity_by_association) |> preload([{:token_transfers, [:token, :from_address, :to_address]}]) @@ -1985,45 +1755,6 @@ defmodule Explorer.Chain do |> Enum.into(%{}) end - @doc """ - Lists the top `t:Explorer.Chain.Token.t/0`'s'. - - """ - @spec list_top_tokens(String.t()) :: [{Token.t(), non_neg_integer()}] - def list_top_tokens(filter, options \\ []) do - paging_options = Keyword.get(options, :paging_options, @default_paging_options) - token_type = Keyword.get(options, :token_type, nil) - sorting = Keyword.get(options, :sorting, []) - - fetch_top_tokens(filter, paging_options, token_type, sorting, options) - end - - defp fetch_top_tokens(filter, paging_options, token_type, sorting, options) do - base_query = Token.base_token_query(token_type, sorting) - - base_query_with_paging = - base_query - |> Token.page_tokens(paging_options, sorting) - |> limit(^paging_options.page_size) - - query = - if filter && filter !== "" do - case Search.prepare_search_term(filter) do - {:some, filter_term} -> - base_query_with_paging - |> where(fragment("to_tsvector('english', symbol || ' ' || name) @@ to_tsquery(?)", ^filter_term)) - - _ -> - base_query_with_paging - end - else - base_query_with_paging - end - - query - |> select_repo(options).all() - end - @doc """ Calls `reducer` on a stream of `t:Explorer.Chain.Block.t/0` without `t:Explorer.Chain.Block.Reward.t/0`. """ @@ -2257,7 +1988,7 @@ defmodule Explorer.Chain do from(t in Transaction, where: not is_nil(t.block_hash) and not is_nil(t.created_contract_address_hash) and - is_nil(t.created_contract_code_indexed_at), + is_nil(t.created_contract_code_indexed_at) and t.status == ^1, select: ^fields ) @@ -2865,12 +2596,12 @@ defmodule Explorer.Chain do options ) do paging_options - |> fetch_transactions() + |> Transaction.fetch_transactions() |> where([transaction], not is_nil(transaction.block_number) and not is_nil(transaction.index)) |> apply_filter_by_method_id_to_transactions(method_id_filter) |> apply_filter_by_tx_type_to_transactions(type_filter) |> join_associations(necessity_by_association) - |> put_has_token_transfers_to_tx(old_ui?) + |> Transaction.put_has_token_transfers_to_tx(old_ui?) |> (&if(old_ui?, do: preload(&1, [{:token_transfers, [:token, :from_address, :to_address]}]), else: &1)).() |> select_repo(options).all() |> (&if(old_ui?, @@ -2915,7 +2646,7 @@ defmodule Explorer.Chain do type_filter = Keyword.get(options, :type) Transaction - |> page_pending_transaction(paging_options) + |> Transaction.page_pending_transaction(paging_options) |> limit(^paging_options.page_size) |> pending_transactions_query() |> apply_filter_by_method_id_to_transactions(method_id_filter) @@ -3466,28 +3197,6 @@ defmodule Explorer.Chain do end end - defp fetch_transactions(paging_options \\ nil, from_block \\ nil, to_block \\ nil, with_pending? \\ false) do - Transaction - |> order_for_transactions(with_pending?) - |> where_block_number_in_period(from_block, to_block) - |> handle_paging_options(paging_options) - end - - defp order_for_transactions(query, true) do - query - |> order_by([transaction], - desc: transaction.block_number, - desc: transaction.index, - desc: transaction.inserted_at, - asc: transaction.hash - ) - end - - defp order_for_transactions(query, _) do - query - |> order_by([transaction], desc: transaction.block_number, desc: transaction.index) - end - defp fetch_transactions_in_ascending_order_by_index(paging_options) do Transaction |> order_by([transaction], asc: transaction.index) @@ -3518,24 +3227,6 @@ defmodule Explorer.Chain do |> limit(^paging_options.page_size) end - defp handle_paging_options(query, nil), do: query - - defp handle_paging_options(query, %PagingOptions{key: nil, page_size: nil}), do: query - - defp handle_paging_options(query, paging_options) do - query - |> page_transaction(paging_options) - |> limit(^paging_options.page_size) - end - - defp handle_verified_contracts_paging_options(query, nil), do: query - - defp handle_verified_contracts_paging_options(query, paging_options) do - query - |> page_verified_contracts(paging_options) - |> limit(^paging_options.page_size) - end - defp handle_withdrawals_paging_options(query, nil), do: query defp handle_withdrawals_paging_options(query, paging_options) do @@ -3551,7 +3242,7 @@ defmodule Explorer.Chain do query |> (&if(paging_options |> Map.get(:page_number, 1) |> process_page_number() == 1, do: &1, - else: page_transaction(&1, paging_options) + else: Transaction.page_transaction(&1, paging_options) )).() |> handle_page(paging_options) end @@ -3594,29 +3285,21 @@ defmodule Explorer.Chain do :required -> from(q in query, inner_join: a in assoc(q, ^association), + as: ^association, left_join: b in assoc(a, ^nested_preload), + as: ^nested_preload, preload: [{^association, {a, [{^nested_preload, b}]}}] ) end end - defp join_association(query, association, necessity) when is_atom(association) do - case necessity do - :optional -> - preload(query, ^association) - - :required -> - from(q in query, inner_join: a in assoc(q, ^association), preload: [{^association, a}]) - end - end - defp join_association(query, association, necessity) do case necessity do :optional -> preload(query, ^association) :required -> - from(q in query, inner_join: a in assoc(q, ^association), preload: [{^association, a}]) + from(q in query, inner_join: a in assoc(q, ^association), as: ^association, preload: [{^association, a}]) end end @@ -3708,46 +3391,6 @@ defmodule Explorer.Chain do where(query, [log], log.index > ^index) end - defp page_pending_transaction(query, %PagingOptions{key: nil}), do: query - - defp page_pending_transaction(query, %PagingOptions{key: {inserted_at, hash}}) do - where( - query, - [transaction], - (is_nil(transaction.block_number) and - (transaction.inserted_at < ^inserted_at or - (transaction.inserted_at == ^inserted_at and transaction.hash > ^hash))) or - not is_nil(transaction.block_number) - ) - end - - defp page_transaction(query, %PagingOptions{key: nil}), do: query - - defp page_transaction(query, %PagingOptions{is_pending_tx: true} = options), - do: page_pending_transaction(query, options) - - defp page_transaction(query, %PagingOptions{key: {block_number, index}, is_index_in_asc_order: true}) do - where( - query, - [transaction], - transaction.block_number < ^block_number or - (transaction.block_number == ^block_number and transaction.index > ^index) - ) - end - - defp page_transaction(query, %PagingOptions{key: {block_number, index}}) do - where( - query, - [transaction], - transaction.block_number < ^block_number or - (transaction.block_number == ^block_number and transaction.index < ^index) - ) - end - - defp page_transaction(query, %PagingOptions{key: {index}}) do - where(query, [transaction], transaction.index < ^index) - end - defp page_block_transactions(query, %PagingOptions{key: nil}), do: query defp page_block_transactions(query, %PagingOptions{key: {_block_number, index}, is_index_in_asc_order: true}) do @@ -3810,12 +3453,6 @@ defmodule Explorer.Chain do ) end - defp page_verified_contracts(query, %PagingOptions{key: nil}), do: query - - defp page_verified_contracts(query, %PagingOptions{key: {id}}) do - where(query, [contract], contract.id < ^id) - end - @doc """ Ensures the following conditions are true: @@ -3845,7 +3482,7 @@ defmodule Explorer.Chain do end @doc """ - The current total number of coins minted minus verifiably burned coins. + The current total number of coins minted minus verifiably burnt coins. """ @spec total_supply :: non_neg_integer() | nil def total_supply do @@ -3892,56 +3529,6 @@ defmodule Explorer.Chain do |> Repo.stream_reduce(initial, reducer) end - @doc """ - Finds all token instances (pairs of contract_address_hash and token_id) which was met in token transfers but has no corresponding entry in token_instances table - """ - @spec stream_not_inserted_token_instances( - initial :: accumulator, - reducer :: (entry :: map(), accumulator -> accumulator) - ) :: {:ok, accumulator} - when accumulator: term() - def stream_not_inserted_token_instances(initial, reducer) when is_function(reducer, 2) do - nft_tokens = - from( - token in Token, - where: token.type == ^"ERC-721" or token.type == ^"ERC-1155", - select: token.contract_address_hash - ) - - token_ids_query = - from( - token_transfer in TokenTransfer, - select: %{ - token_contract_address_hash: token_transfer.token_contract_address_hash, - token_id: fragment("unnest(?)", token_transfer.token_ids) - } - ) - - query = - from( - transfer in subquery(token_ids_query), - inner_join: token in subquery(nft_tokens), - on: token.contract_address_hash == transfer.token_contract_address_hash, - left_join: instance in Instance, - on: - transfer.token_contract_address_hash == instance.token_contract_address_hash and - transfer.token_id == instance.token_id, - where: is_nil(instance.token_id), - select: %{ - contract_address_hash: transfer.token_contract_address_hash, - token_id: transfer.token_id - } - ) - - distinct_query = - from( - q in subquery(query), - distinct: [q.contract_address_hash, q.token_id] - ) - - Repo.stream_reduce(distinct_query, initial, reducer) - end - @doc """ Finds all token instances where metadata never tried to fetch """ @@ -4003,32 +3590,6 @@ defmodule Explorer.Chain do |> Repo.stream_reduce(initial, reducer) end - @doc """ - Returns a list of block numbers token transfer `t:Log.t/0`s that don't have an - associated `t:TokenTransfer.t/0` record. - """ - def uncataloged_token_transfer_block_numbers do - query = - from(l in Log, - as: :log, - where: - l.first_topic == unquote(TokenTransfer.constant()) or - l.first_topic == unquote(TokenTransfer.erc1155_single_transfer_signature()) or - l.first_topic == unquote(TokenTransfer.erc1155_batch_transfer_signature()), - where: - not exists( - from(tf in TokenTransfer, - where: tf.transaction_hash == parent_as(:log).transaction_hash, - where: tf.log_index == parent_as(:log).index - ) - ), - select: l.block_number, - distinct: l.block_number - ) - - Repo.stream_reduce(query, [], &[&1 | &2]) - end - def decode_contract_address_hash_response(resp) do case resp do "0x000000000000000000000000" <> address -> @@ -4059,12 +3620,9 @@ defmodule Explorer.Chain do `:required`, and the `t:Token.t/0` has no associated record for that association, then the `t:Token.t/0` will not be included in the list. """ - @spec token_from_address_hash(Hash.Address.t(), [necessity_by_association_option | api?]) :: + @spec token_from_address_hash(Hash.Address.t() | String.t(), [necessity_by_association_option | api?]) :: {:ok, Token.t()} | {:error, :not_found} - def token_from_address_hash( - %Hash{byte_count: unquote(Hash.Address.byte_count())} = hash, - options \\ [] - ) do + def token_from_address_hash(hash, options \\ []) do necessity_by_association = Keyword.get(options, :necessity_by_association, %{}) query = @@ -4126,13 +3684,6 @@ defmodule Explorer.Chain do Repo.exists?(query) end - @spec address_has_rewards?(Address.t()) :: boolean() - def address_has_rewards?(address_hash) do - query = from(r in Reward, where: r.address_hash == ^address_hash) - - Repo.exists?(query) - end - @spec address_tokens_with_balance(Hash.Address.t(), [any()]) :: [] def address_tokens_with_balance(address_hash, paging_options \\ []) do address_hash @@ -4249,14 +3800,6 @@ defmodule Explorer.Chain do ) end - @doc """ - Inserts list of token instances via upsert_token_instance/1. - """ - @spec upsert_token_instances_list([map()]) :: list() - def upsert_token_instances_list(instances) do - Enum.map(instances, &upsert_token_instance/1) - end - @doc """ Update a new `t:Token.t/0` record. @@ -4614,27 +4157,40 @@ defmodule Explorer.Chain do Repo.one!(query, timeout: :infinity) end - @spec address_to_unique_tokens(Hash.Address.t(), [paging_options | api?]) :: [Instance.t()] - def address_to_unique_tokens(contract_address_hash, options \\ []) do + @spec address_to_unique_tokens(Hash.Address.t(), Token.t(), [paging_options | api?]) :: [Instance.t()] + def address_to_unique_tokens(contract_address_hash, token, options \\ []) do paging_options = Keyword.get(options, :paging_options, @default_paging_options) contract_address_hash |> Instance.address_to_unique_token_instances() |> Instance.page_token_instance(paging_options) |> limit(^paging_options.page_size) + |> preload([_], [:owner]) |> select_repo(options).all() - |> Enum.map(&put_owner_to_token_instance(&1, options)) + |> Enum.map(&put_owner_to_token_instance(&1, token, options)) end - def put_owner_to_token_instance(%Instance{} = token_instance, options \\ []) do - owner = + def put_owner_to_token_instance(token_instance, token, options \\ []) + + def put_owner_to_token_instance(%Instance{is_unique: nil} = token_instance, token, options) do + put_owner_to_token_instance(Instance.put_is_unique(token_instance, token, options), token, options) + end + + def put_owner_to_token_instance( + %Instance{owner: nil, is_unique: true} = token_instance, + %Token{type: "ERC-1155"}, + options + ) do + owner_address_hash = token_instance |> Instance.owner_query() |> select_repo(options).one() - %{token_instance | owner: owner} + %{token_instance | owner: select_repo(options).get_by(Address, hash: owner_address_hash)} end + def put_owner_to_token_instance(%Instance{} = token_instance, _token, _options), do: token_instance + @spec data() :: Dataloader.Ecto.t() def data, do: DataloaderEcto.new(Repo) @@ -5214,7 +4770,7 @@ defmodule Explorer.Chain do if transaction_index == 0 do 0 else - filtered_block_numbers = EthereumJSONRPC.are_block_numbers_in_range?([block_number]) + filtered_block_numbers = RangesHelper.filter_traceable_block_numbers([block_number]) {:ok, traces} = fetch_block_internal_transactions(filtered_block_numbers, json_rpc_named_arguments) sorted_traces = @@ -5288,10 +4844,12 @@ defmodule Explorer.Chain do end end - defp from_block(options) do + @spec from_block(keyword) :: any + def from_block(options) do Keyword.get(options, :from_block) || nil end + @spec to_block(keyword) :: any def to_block(options) do Keyword.get(options, :to_block) || nil end @@ -5465,54 +5023,6 @@ defmodule Explorer.Chain do dynamic([tx, created_token: created_token], ^dynamic or not is_nil(created_token)) end - @spec verified_contracts([ - paging_options - | necessity_by_association_option - | {:filter, :solidity | :vyper} - | {:search, String.t() | {:api?, true | false}} - ]) :: [SmartContract.t()] - def verified_contracts(options \\ []) do - paging_options = Keyword.get(options, :paging_options, @default_paging_options) - necessity_by_association = Keyword.get(options, :necessity_by_association, %{}) - filter = Keyword.get(options, :filter, nil) - search_string = Keyword.get(options, :search, nil) - - query = from(contract in SmartContract, select: contract, order_by: [desc: :id]) - - query - |> filter_contracts(filter) - |> search_contracts(search_string) - |> handle_verified_contracts_paging_options(paging_options) - |> join_associations(necessity_by_association) - |> select_repo(options).all() - end - - defp search_contracts(basic_query, nil), do: basic_query - - defp search_contracts(basic_query, search_string) do - from(contract in basic_query, - where: - ilike(contract.name, ^"%#{search_string}%") or - ilike(fragment("'0x' || encode(?, 'hex')", contract.address_hash), ^"%#{search_string}%") - ) - end - - defp filter_contracts(basic_query, :solidity) do - basic_query - |> where(is_vyper_contract: ^false) - end - - defp filter_contracts(basic_query, :vyper) do - basic_query - |> where(is_vyper_contract: ^true) - end - - defp filter_contracts(basic_query, :yul) do - from(query in basic_query, where: is_nil(query.abi)) - end - - defp filter_contracts(basic_query, _), do: basic_query - def count_verified_contracts do Repo.aggregate(SmartContract, :count, timeout: :infinity) end @@ -5763,20 +5273,6 @@ defmodule Explorer.Chain do limit(query, ^coin_balances_fetcher_limit) end - def put_has_token_transfers_to_tx(query, true), do: query - - def put_has_token_transfers_to_tx(query, false) do - from(tx in query, - select_merge: %{ - has_token_transfers: - fragment( - "(SELECT transaction_hash FROM token_transfers WHERE transaction_hash = ? LIMIT 1) IS NOT NULL", - tx.hash - ) - } - ) - end - @spec default_paging_options() :: map() def default_paging_options do @default_paging_options diff --git a/apps/explorer/lib/explorer/chain/address.ex b/apps/explorer/lib/explorer/chain/address.ex index e8ea2300590a..41bac5f23cf4 100644 --- a/apps/explorer/lib/explorer/chain/address.ex +++ b/apps/explorer/lib/explorer/chain/address.ex @@ -7,6 +7,7 @@ defmodule Explorer.Chain.Address do use Explorer.Schema + alias Ecto.Association.NotLoaded alias Ecto.Changeset alias Explorer.{Chain, PagingOptions} @@ -46,7 +47,8 @@ defmodule Explorer.Chain.Address do contract has been verified * `names` - names known for the address * `inserted_at` - when this address was inserted - * `updated_at` when this address was last updated + * `updated_at` - when this address was last updated + * `ens_domain_name` - virtual field for ENS domain name passing `fetched_coin_balance` and `fetched_coin_balance_block_number` may be updated when a new coin_balance row is fetched. They may also be updated when the balance is fetched via the on demand fetcher. @@ -63,7 +65,8 @@ defmodule Explorer.Chain.Address do nonce: non_neg_integer() | nil, transactions_count: non_neg_integer() | nil, token_transfers_count: non_neg_integer() | nil, - gas_used: non_neg_integer() | nil + gas_used: non_neg_integer() | nil, + ens_domain_name: String.t() | nil } @derive {Poison.Encoder, @@ -103,6 +106,7 @@ defmodule Explorer.Chain.Address do field(:transactions_count, :integer) field(:token_transfers_count, :integer) field(:gas_used, :integer) + field(:ens_domain_name, :string, virtual: true) has_one(:smart_contract, SmartContract) has_one(:token, Token, foreign_key: :contract_address_hash) @@ -157,6 +161,11 @@ defmodule Explorer.Chain.Address do checksum(hash, iodata?) end + def checksum(hash_string, iodata?) when is_binary(hash_string) do + {:ok, hash} = Chain.string_to_address_hash(hash_string) + checksum(hash, iodata?) + end + def checksum(hash, iodata?) do checksum_formatted = case Application.get_env(:explorer, :checksum_function) || :eth do @@ -332,6 +341,15 @@ defmodule Explorer.Chain.Address do end end + @doc """ + Checks if given address is smart-contract + """ + @spec is_smart_contract(any()) :: boolean() | nil + def is_smart_contract(%__MODULE__{contract_code: nil}), do: false + def is_smart_contract(%__MODULE__{contract_code: _}), do: true + def is_smart_contract(%NotLoaded{}), do: nil + def is_smart_contract(_), do: false + defp get_addresses(options) do accounts_with_n = fetch_top_addresses(options) diff --git a/apps/explorer/lib/explorer/chain/address/token_balance.ex b/apps/explorer/lib/explorer/chain/address/token_balance.ex index ab7a75b62486..5b647bae1ed2 100644 --- a/apps/explorer/lib/explorer/chain/address/token_balance.ex +++ b/apps/explorer/lib/explorer/chain/address/token_balance.ex @@ -13,6 +13,7 @@ defmodule Explorer.Chain.Address.TokenBalance do alias Explorer.Chain alias Explorer.Chain.Address.TokenBalance + alias Explorer.Chain.Cache.BackgroundMigrations alias Explorer.Chain.{Address, Block, Hash, Token} @typedoc """ @@ -80,15 +81,27 @@ defmodule Explorer.Chain.Address.TokenBalance do ignores the burn_address for tokens ERC-721 since the most tokens ERC-721 don't allow get the balance for burn_address. """ + # credo:disable-for-next-line /Complexity/ def unfetched_token_balances do - from( - tb in TokenBalance, - join: t in Token, - on: tb.token_contract_address_hash == t.contract_address_hash, - where: - ((tb.address_hash != ^@burn_address_hash and t.type == "ERC-721") or t.type == "ERC-20" or t.type == "ERC-1155") and - (is_nil(tb.value_fetched_at) or is_nil(tb.value)) - ) + if BackgroundMigrations.get_tb_token_type_finished() do + from( + tb in TokenBalance, + where: + ((tb.address_hash != ^@burn_address_hash and tb.token_type == "ERC-721") or tb.token_type == "ERC-20" or + tb.token_type == "ERC-1155") and + (is_nil(tb.value_fetched_at) or is_nil(tb.value)) + ) + else + from( + tb in TokenBalance, + join: t in Token, + on: tb.token_contract_address_hash == t.contract_address_hash, + where: + ((tb.address_hash != ^@burn_address_hash and t.type == "ERC-721") or t.type == "ERC-20" or + t.type == "ERC-1155") and + (is_nil(tb.value_fetched_at) or is_nil(tb.value)) + ) + end end @doc """ diff --git a/apps/explorer/lib/explorer/chain/block.ex b/apps/explorer/lib/explorer/chain/block.ex index b41941fdc91f..fae356f3bcfa 100644 --- a/apps/explorer/lib/explorer/chain/block.ex +++ b/apps/explorer/lib/explorer/chain/block.ex @@ -8,7 +8,8 @@ defmodule Explorer.Chain.Block do use Explorer.Schema alias Explorer.Chain.{Address, Block, Gas, Hash, PendingBlockOperation, Transaction, Wei, Withdrawal} - alias Explorer.Chain.Block.{Reward, SecondDegreeRelation} + alias Explorer.Chain.Block.{EmissionReward, Reward, SecondDegreeRelation} + alias Explorer.Repo @optional_attrs ~w(size refetch_needed total_difficulty difficulty base_fee_per_gas)a |> (&(case Application.compile_env(:explorer, :chain_type) do @@ -219,4 +220,89 @@ defmodule Explorer.Chain.Block do order_by: [desc: block.number] ) end + + @doc """ + Calculates transaction fees (gas price * gas used) for the list of transactions (from a single block) + """ + @spec transaction_fees([Transaction.t()]) :: Decimal.t() + def transaction_fees(transactions) do + Enum.reduce(transactions, Decimal.new(0), fn %{gas_used: gas_used, gas_price: gas_price}, acc -> + if gas_price do + gas_used + |> Decimal.new() + |> Decimal.mult(gas_price_to_decimal(gas_price)) + |> Decimal.add(acc) + else + acc + end + end) + end + + defp gas_price_to_decimal(nil), do: nil + defp gas_price_to_decimal(%Wei{} = wei), do: wei.value + defp gas_price_to_decimal(gas_price), do: Decimal.new(gas_price) + + @doc """ + Calculates burnt fees for the list of transactions (from a single block) + """ + @spec burnt_fees(list(), Wei.t() | nil) :: Wei.t() | nil + def burnt_fees(transactions, base_fee_per_gas) do + total_gas_used = + transactions + |> Enum.reduce(Decimal.new(0), fn %{gas_used: gas_used}, acc -> + gas_used + |> Decimal.new() + |> Decimal.add(acc) + end) + + if is_nil(base_fee_per_gas) do + nil + else + Wei.mult(base_fee_per_gas_to_wei(base_fee_per_gas), total_gas_used) + end + end + + defp base_fee_per_gas_to_wei(%Wei{} = wei), do: wei + defp base_fee_per_gas_to_wei(base_fee_per_gas), do: %Wei{value: Decimal.new(base_fee_per_gas)} + + @uncle_reward_coef 1 / 32 + @spec block_reward_by_parts(Block.t(), [Transaction.t()]) :: %{ + block_number: block_number(), + block_hash: Hash.Full.t(), + miner_hash: Hash.Address.t(), + static_reward: any(), + transaction_fees: any(), + burnt_fees: Wei.t() | nil, + uncle_reward: Wei.t() | nil | false + } + def block_reward_by_parts(block, transactions) do + %{hash: block_hash, number: block_number} = block + base_fee_per_gas = Map.get(block, :base_fee_per_gas) + + transaction_fees = transaction_fees(transactions) + + static_reward = + Repo.one( + from( + er in EmissionReward, + where: fragment("int8range(?, ?) <@ ?", ^block_number, ^(block_number + 1), er.block_range), + select: er.reward + ) + ) || %Wei{value: Decimal.new(0)} + + has_uncles? = is_list(block.uncles) and not Enum.empty?(block.uncles) + + burnt_fees = burnt_fees(transactions, base_fee_per_gas) + uncle_reward = (has_uncles? && Wei.mult(static_reward, Decimal.from_float(@uncle_reward_coef))) || nil + + %{ + block_number: block_number, + block_hash: block_hash, + miner_hash: block.miner_hash, + static_reward: static_reward, + transaction_fees: %Wei{value: transaction_fees}, + burnt_fees: burnt_fees || %Wei{value: Decimal.new(0)}, + uncle_reward: uncle_reward || %Wei{value: Decimal.new(0)} + } + end end diff --git a/apps/explorer/lib/explorer/chain/block/reward.ex b/apps/explorer/lib/explorer/chain/block/reward.ex index 0ec4f05e881e..d45df2666669 100644 --- a/apps/explorer/lib/explorer/chain/block/reward.ex +++ b/apps/explorer/lib/explorer/chain/block/reward.ex @@ -6,7 +6,7 @@ defmodule Explorer.Chain.Block.Reward do use Explorer.Schema alias Explorer.Application.Constants - alias Explorer.{Chain, PagingOptions} + alias Explorer.{Chain, PagingOptions, Repo} alias Explorer.Chain.Block.Reward.AddressType alias Explorer.Chain.{Address, Block, Hash, Validator, Wei} alias Explorer.Chain.Fetcher.FetchValidatorInfoOnDemand @@ -278,4 +278,14 @@ defmodule Explorer.Chain.Block.Reward do query end end + + @doc """ + Checks if an address has rewards + """ + @spec address_has_rewards?(Hash.Address.t()) :: boolean() + def address_has_rewards?(address_hash) do + query = from(r in __MODULE__, where: r.address_hash == ^address_hash) + + Repo.exists?(query) + end end diff --git a/apps/explorer/lib/explorer/chain/cache/background_migrations.ex b/apps/explorer/lib/explorer/chain/cache/background_migrations.ex new file mode 100644 index 000000000000..3470f48c08b7 --- /dev/null +++ b/apps/explorer/lib/explorer/chain/cache/background_migrations.ex @@ -0,0 +1,45 @@ +defmodule Explorer.Chain.Cache.BackgroundMigrations do + @moduledoc """ + Caches background migrations' status. + """ + + require Logger + + use Explorer.Chain.MapCache, + name: :background_migrations_status, + key: :denormalization_finished, + key: :tb_token_type_finished, + key: :ctb_token_type_finished + + @dialyzer :no_match + + alias Explorer.Migrator.{ + AddressCurrentTokenBalanceTokenType, + AddressTokenBalanceTokenType, + TransactionsDenormalization + } + + defp handle_fallback(:denormalization_finished) do + Task.start(fn -> + set_denormalization_finished(TransactionsDenormalization.migration_finished?()) + end) + + {:return, false} + end + + defp handle_fallback(:tb_token_type_finished) do + Task.start(fn -> + set_tb_token_type_finished(AddressTokenBalanceTokenType.migration_finished?()) + end) + + {:return, false} + end + + defp handle_fallback(:ctb_token_type_finished) do + Task.start(fn -> + set_ctb_token_type_finished(AddressCurrentTokenBalanceTokenType.migration_finished?()) + end) + + {:return, false} + end +end diff --git a/apps/explorer/lib/explorer/chain/cache/gas_price_oracle.ex b/apps/explorer/lib/explorer/chain/cache/gas_price_oracle.ex index e147c9c0c782..d6d85c2b42f2 100644 --- a/apps/explorer/lib/explorer/chain/cache/gas_price_oracle.ex +++ b/apps/explorer/lib/explorer/chain/cache/gas_price_oracle.ex @@ -14,151 +14,326 @@ defmodule Explorer.Chain.Cache.GasPriceOracle do alias Explorer.Chain.{ Block, + DenormalizationHelper, + Transaction, Wei } - alias Explorer.Repo + alias Explorer.Counters.AverageBlockTime + alias Explorer.{Market, Repo} + alias Timex.Duration use Explorer.Chain.MapCache, name: :gas_price, key: :gas_prices, + key: :gas_prices_acc, + key: :updated_at, key: :old_gas_prices, key: :async_task, - global_ttl: Application.get_env(:explorer, __MODULE__)[:global_ttl], + global_ttl: global_ttl(), ttl_check_interval: :timer.seconds(1), callback: &async_task_on_deletion(&1) @doc """ - Get `safelow`, `average` and `fast` percentile of transactions gas prices among the last `num_of_blocks` blocks + Calculates the `slow`, `average`, and `fast` gas price and time percentiles from the last `num_of_blocks` blocks and estimates the fiat price for each percentile. + These percentiles correspond to the likelihood of a transaction being picked up by miners depending on the fee offered. """ @spec get_average_gas_price(pos_integer(), pos_integer(), pos_integer(), pos_integer()) :: - {:error, any} | {:ok, %{String.t() => nil | float, String.t() => nil | float, String.t() => nil | float}} + {{:error, any} | {:ok, %{slow: gas_price, average: gas_price, fast: gas_price}}, + [ + %{ + block_number: non_neg_integer(), + slow_gas_price: nil | Decimal.t(), + fast_gas_price: nil | Decimal.t(), + average_gas_price: nil | Decimal.t(), + slow_priority_fee_per_gas: nil | Decimal.t(), + average_priority_fee_per_gas: nil | Decimal.t(), + fast_priority_fee_per_gas: nil | Decimal.t(), + slow_time: nil | Decimal.t(), + average_time: nil | Decimal.t(), + fast_time: nil | Decimal.t() + } + ]} + when gas_price: nil | %{price: float(), time: float(), fiat_price: Decimal.t()} def get_average_gas_price(num_of_blocks, safelow_percentile, average_percentile, fast_percentile) do safelow_percentile_fraction = safelow_percentile / 100 average_percentile_fraction = average_percentile / 100 fast_percentile_fraction = fast_percentile / 100 + acc = get_gas_prices_acc() + + from_block = + case acc do + [%{block_number: from_block} | _] -> from_block + _ -> -1 + end + + average_block_time = + case AverageBlockTime.average_block_time() do + {:error, _} -> nil + average_block_time -> average_block_time |> Duration.to_milliseconds() + end + fee_query = - from( - block in Block, - left_join: transaction in assoc(block, :transactions), - where: block.consensus == true, - where: transaction.status == ^1, - where: transaction.gas_price > ^0, - group_by: block.number, - order_by: [desc: block.number], - select: %{ - slow_gas_price: - fragment( - "percentile_disc(?) within group ( order by ? )", - ^safelow_percentile_fraction, - transaction.gas_price - ), - average_gas_price: - fragment( - "percentile_disc(?) within group ( order by ? )", - ^average_percentile_fraction, - transaction.gas_price - ), - fast_gas_price: - fragment( - "percentile_disc(?) within group ( order by ? )", - ^fast_percentile_fraction, - transaction.gas_price - ), - slow: - fragment( - "percentile_disc(?) within group ( order by ? )", - ^safelow_percentile_fraction, - transaction.max_priority_fee_per_gas - ), - average: - fragment( - "percentile_disc(?) within group ( order by ? )", - ^average_percentile_fraction, - transaction.max_priority_fee_per_gas - ), - fast: - fragment( - "percentile_disc(?) within group ( order by ? )", - ^fast_percentile_fraction, - transaction.max_priority_fee_per_gas - ) - }, - limit: ^num_of_blocks - ) - - gas_prices = fee_query |> Repo.all(timeout: :infinity) |> process_fee_data_from_db() - - {:ok, gas_prices} + if DenormalizationHelper.denormalization_finished?() do + from( + transaction in Transaction, + where: transaction.block_consensus == true, + where: transaction.status == ^1, + where: transaction.gas_price > ^0, + where: transaction.block_number > ^from_block, + group_by: transaction.block_number, + order_by: [desc: transaction.block_number], + select: %{ + block_number: transaction.block_number, + slow_gas_price: + fragment( + "percentile_disc(? :: real) within group ( order by ? )", + ^safelow_percentile_fraction, + transaction.gas_price + ), + average_gas_price: + fragment( + "percentile_disc(? :: real) within group ( order by ? )", + ^average_percentile_fraction, + transaction.gas_price + ), + fast_gas_price: + fragment( + "percentile_disc(? :: real) within group ( order by ? )", + ^fast_percentile_fraction, + transaction.gas_price + ), + slow_priority_fee_per_gas: + fragment( + "percentile_disc(? :: real) within group ( order by ? )", + ^safelow_percentile_fraction, + transaction.max_priority_fee_per_gas + ), + average_priority_fee_per_gas: + fragment( + "percentile_disc(? :: real) within group ( order by ? )", + ^average_percentile_fraction, + transaction.max_priority_fee_per_gas + ), + fast_priority_fee_per_gas: + fragment( + "percentile_disc(? :: real) within group ( order by ? )", + ^fast_percentile_fraction, + transaction.max_priority_fee_per_gas + ), + slow_time: + fragment( + "percentile_disc(? :: real) within group ( order by coalesce(extract(milliseconds from (?)::interval), ?) desc )", + ^safelow_percentile_fraction, + transaction.block_timestamp - transaction.earliest_processing_start, + ^average_block_time + ), + average_time: + fragment( + "percentile_disc(? :: real) within group ( order by coalesce(extract(milliseconds from (?)::interval), ?) desc )", + ^average_percentile_fraction, + transaction.block_timestamp - transaction.earliest_processing_start, + ^average_block_time + ), + fast_time: + fragment( + "percentile_disc(? :: real) within group ( order by coalesce(extract(milliseconds from (?)::interval), ?) desc )", + ^fast_percentile_fraction, + transaction.block_timestamp - transaction.earliest_processing_start, + ^average_block_time + ) + }, + limit: ^num_of_blocks + ) + else + from( + block in Block, + left_join: transaction in assoc(block, :transactions), + where: block.consensus == true, + where: transaction.status == ^1, + where: transaction.gas_price > ^0, + where: transaction.block_number > ^from_block, + group_by: transaction.block_number, + order_by: [desc: transaction.block_number], + select: %{ + block_number: transaction.block_number, + slow_gas_price: + fragment( + "percentile_disc(? :: real) within group ( order by ? )", + ^safelow_percentile_fraction, + transaction.gas_price + ), + average_gas_price: + fragment( + "percentile_disc(? :: real) within group ( order by ? )", + ^average_percentile_fraction, + transaction.gas_price + ), + fast_gas_price: + fragment( + "percentile_disc(? :: real) within group ( order by ? )", + ^fast_percentile_fraction, + transaction.gas_price + ), + slow_priority_fee_per_gas: + fragment( + "percentile_disc(? :: real) within group ( order by ? )", + ^safelow_percentile_fraction, + transaction.max_priority_fee_per_gas + ), + average_priority_fee_per_gas: + fragment( + "percentile_disc(? :: real) within group ( order by ? )", + ^average_percentile_fraction, + transaction.max_priority_fee_per_gas + ), + fast_priority_fee_per_gas: + fragment( + "percentile_disc(? :: real) within group ( order by ? )", + ^fast_percentile_fraction, + transaction.max_priority_fee_per_gas + ), + slow_time: + fragment( + "percentile_disc(? :: real) within group ( order by coalesce(extract(milliseconds from (?)::interval), ?) desc )", + ^safelow_percentile_fraction, + block.timestamp - transaction.earliest_processing_start, + ^average_block_time + ), + average_time: + fragment( + "percentile_disc(? :: real) within group ( order by coalesce(extract(milliseconds from (?)::interval), ?) desc )", + ^average_percentile_fraction, + block.timestamp - transaction.earliest_processing_start, + ^average_block_time + ), + fast_time: + fragment( + "percentile_disc(? :: real) within group ( order by coalesce(extract(milliseconds from (?)::interval), ?) desc )", + ^fast_percentile_fraction, + block.timestamp - transaction.earliest_processing_start, + ^average_block_time + ) + }, + limit: ^num_of_blocks + ) + end + + new_acc = fee_query |> Repo.all(timeout: :infinity) |> merge_gas_prices(acc, num_of_blocks) + + gas_prices = new_acc |> process_fee_data_from_db() + + {{:ok, gas_prices}, new_acc} catch error -> - {:error, error} + Logger.error("Failed to get gas prices: #{inspect(error)}") + {{:error, error}, get_gas_prices_acc()} end + defp merge_gas_prices(new, acc, acc_size), do: Enum.take(new ++ acc, acc_size) + defp process_fee_data_from_db([]) do %{ - "slow" => nil, - "average" => nil, - "fast" => nil + slow: nil, + average: nil, + fast: nil } end defp process_fee_data_from_db(fees) do - fees_length = Enum.count(fees) - %{ slow_gas_price: slow_gas_price, average_gas_price: average_gas_price, fast_gas_price: fast_gas_price, - slow: slow, - average: average, - fast: fast - } = - fees - |> Enum.reduce( - &Map.merge(&1, &2, fn - _, v1, v2 when nil not in [v1, v2] -> Decimal.add(v1, v2) - _, v1, v2 -> v1 || v2 - end) - ) - |> Map.new(fn - {key, nil} -> {key, nil} - {key, value} -> {key, Decimal.div(value, fees_length)} - end) + slow_priority_fee_per_gas: slow_priority_fee_per_gas, + average_priority_fee_per_gas: average_priority_fee_per_gas, + fast_priority_fee_per_gas: fast_priority_fee_per_gas, + slow_time: slow_time, + average_time: average_time, + fast_time: fast_time + } = merge_fees(fees) json_rpc_named_arguments = Application.get_env(:explorer, :json_rpc_named_arguments) {slow_fee, average_fee, fast_fee} = - case {nil not in [slow, average, fast], EthereumJSONRPC.fetch_block_by_tag("pending", json_rpc_named_arguments)} do - {true, {:ok, %Blocks{blocks_params: [%{base_fee_per_gas: base_fee}]}}} when not is_nil(base_fee) -> + case nil not in [slow_priority_fee_per_gas, average_priority_fee_per_gas, fast_priority_fee_per_gas] && + EthereumJSONRPC.fetch_block_by_tag("pending", json_rpc_named_arguments) do + {:ok, %Blocks{blocks_params: [%{base_fee_per_gas: base_fee}]}} when not is_nil(base_fee) -> base_fee_wei = base_fee |> Decimal.new() |> Wei.from(:wei) { - priority_with_base_fee(slow, base_fee_wei), - priority_with_base_fee(average, base_fee_wei), - priority_with_base_fee(fast, base_fee_wei) + priority_with_base_fee(slow_priority_fee_per_gas, base_fee_wei), + priority_with_base_fee(average_priority_fee_per_gas, base_fee_wei), + priority_with_base_fee(fast_priority_fee_per_gas, base_fee_wei) } _ -> {gas_price(slow_gas_price), gas_price(average_gas_price), gas_price(fast_gas_price)} end + exchange_rate_from_db = Market.get_coin_exchange_rate() + %{ - "slow" => slow_fee, - "average" => average_fee, - "fast" => fast_fee + slow: compose_gas_price(slow_fee, slow_time, exchange_rate_from_db), + average: compose_gas_price(average_fee, average_time, exchange_rate_from_db), + fast: compose_gas_price(fast_fee, fast_time, exchange_rate_from_db) } end + defp merge_fees(fees_from_db) do + fees_from_db + |> Stream.map(&Map.delete(&1, :block_number)) + |> Enum.reduce( + &Map.merge(&1, &2, fn + _, nil, nil -> nil + _, val, acc when nil not in [val, acc] and is_list(acc) -> [val | acc] + _, val, acc when nil not in [val, acc] -> [val, acc] + _, val, acc -> [val || acc] + end) + ) + |> Map.new(fn + {key, nil} -> + {key, nil} + + {key, value} -> + value = if is_list(value), do: value, else: [value] + count = Enum.count(value) + {key, value |> Enum.reduce(Decimal.new(0), &Decimal.add/2) |> Decimal.div(count)} + end) + end + + defp compose_gas_price(fee, time, exchange_rate_from_db) do + %{ + price: fee |> format_wei(), + time: time && time |> Decimal.to_float(), + fiat_price: fiat_fee(fee, exchange_rate_from_db) + } + end + + defp fiat_fee(fee, exchange_rate) do + exchange_rate.usd_value && + fee + |> Wei.to(:ether) + |> Decimal.mult(exchange_rate.usd_value) + |> Decimal.mult(simple_transaction_gas()) + |> Decimal.round(2) + end + defp priority_with_base_fee(priority, base_fee) do - priority |> Wei.from(:wei) |> Wei.sum(base_fee) |> Wei.to(:gwei) |> Decimal.to_float() |> Float.ceil(2) + priority |> Wei.from(:wei) |> Wei.sum(base_fee) end defp gas_price(value) do - value |> Wei.from(:wei) |> Wei.to(:gwei) |> Decimal.to_float() |> Float.ceil(2) + value |> Wei.from(:wei) end + defp format_wei(wei), do: wei |> Wei.to(:gwei) |> Decimal.to_float() |> Float.ceil(2) + + def global_ttl, do: Application.get_env(:explorer, __MODULE__)[:global_ttl] + + defp simple_transaction_gas, do: Application.get_env(:explorer, __MODULE__)[:simple_transaction_gas] + defp num_of_blocks, do: Application.get_env(:explorer, __MODULE__)[:num_of_blocks] defp safelow, do: Application.get_env(:explorer, __MODULE__)[:safelow_percentile] @@ -181,12 +356,14 @@ defmodule Explorer.Chain.Cache.GasPriceOracle do {:ok, task} = Task.start(fn -> try do - result = get_average_gas_price(num_of_blocks(), safelow(), average(), fast()) + {result, acc} = get_average_gas_price(num_of_blocks(), safelow(), average(), fast()) + set_gas_prices_acc(acc) set_gas_prices(result) + set_updated_at(DateTime.utc_now()) rescue e -> - Logger.debug([ + Logger.error([ "Couldn't update gas used gas_prices", Exception.format(:error, e, __STACKTRACE__) ]) @@ -198,6 +375,10 @@ defmodule Explorer.Chain.Cache.GasPriceOracle do {:update, task} end + defp handle_fallback(:gas_prices_acc) do + {:return, []} + end + defp handle_fallback(_), do: {:return, nil} # By setting this as a `callback` an async task will be started each time the diff --git a/apps/explorer/lib/explorer/chain/contract_method.ex b/apps/explorer/lib/explorer/chain/contract_method.ex index 05a82406c95d..dd1cf0a80923 100644 --- a/apps/explorer/lib/explorer/chain/contract_method.ex +++ b/apps/explorer/lib/explorer/chain/contract_method.ex @@ -5,6 +5,7 @@ defmodule Explorer.Chain.ContractMethod do require Logger + import Ecto.Query, only: [from: 2] use Explorer.Schema alias Explorer.Chain.{Hash, MethodIdentifier, SmartContract} @@ -69,6 +70,18 @@ defmodule Explorer.Chain.ContractMethod do end end + @doc """ + Finds limited number of contract methods by selector id + """ + @spec find_contract_method_query(binary(), integer()) :: Ecto.Query.t() + def find_contract_method_query(method_id, limit) do + from( + contract_method in __MODULE__, + where: contract_method.identifier == ^method_id, + limit: ^limit + ) + end + defp abi_element_to_contract_method(element) do case ABI.parse_specification([element], include_events?: true) do [selector] -> diff --git a/apps/explorer/lib/explorer/chain/csv_export/address_token_transfer_csv_exporter.ex b/apps/explorer/lib/explorer/chain/csv_export/address_token_transfer_csv_exporter.ex index eac64ed3da89..6df94acbeb75 100644 --- a/apps/explorer/lib/explorer/chain/csv_export/address_token_transfer_csv_exporter.ex +++ b/apps/explorer/lib/explorer/chain/csv_export/address_token_transfer_csv_exporter.ex @@ -12,7 +12,7 @@ defmodule Explorer.Chain.CSVExport.AddressTokenTransferCsvExporter do ] alias Explorer.{Chain, PagingOptions, Repo} - alias Explorer.Chain.{Address, Hash, TokenTransfer} + alias Explorer.Chain.{Address, DenormalizationHelper, Hash, TokenTransfer, Transaction} alias Explorer.Chain.CSVExport.Helper @paging_options %PagingOptions{page_size: Helper.limit(), asc_order: true} @@ -69,7 +69,7 @@ defmodule Explorer.Chain.CSVExport.AddressTokenTransferCsvExporter do [ to_string(token_transfer.transaction_hash), token_transfer.transaction.block_number, - token_transfer.transaction.block.timestamp, + Transaction.block_timestamp(token_transfer.transaction), Address.checksum(token_transfer.from_address_hash), Address.checksum(token_transfer.to_address_hash), Address.checksum(token_transfer.token_contract_address_hash), @@ -120,7 +120,7 @@ defmodule Explorer.Chain.CSVExport.AddressTokenTransferCsvExporter do query |> handle_token_transfer_paging_options(paging_options) - |> preload(transaction: :block) + |> preload(^DenormalizationHelper.extend_transaction_preload([:transaction])) |> preload(:token) |> Repo.all() end diff --git a/apps/explorer/lib/explorer/chain/csv_export/address_transaction_csv_exporter.ex b/apps/explorer/lib/explorer/chain/csv_export/address_transaction_csv_exporter.ex index cd2595a7e3a7..600d53d63aa3 100644 --- a/apps/explorer/lib/explorer/chain/csv_export/address_transaction_csv_exporter.ex +++ b/apps/explorer/lib/explorer/chain/csv_export/address_transaction_csv_exporter.ex @@ -10,18 +10,12 @@ defmodule Explorer.Chain.CSVExport.AddressTransactionCsvExporter do alias Explorer.{Chain, Market, PagingOptions, Repo} alias Explorer.Market.MarketHistory - alias Explorer.Chain.{Address, Transaction, Wei} + alias Explorer.Chain.{Address, DenormalizationHelper, Hash, Transaction, Wei} alias Explorer.Chain.CSVExport.Helper - @necessity_by_association [ - necessity_by_association: %{ - :block => :required - } - ] - @paging_options %PagingOptions{page_size: Helper.limit()} - @spec export(Address.t(), String.t(), String.t(), String.t() | nil, String.t() | nil) :: Enumerable.t() + @spec export(Hash.Address.t(), String.t(), String.t(), String.t() | nil, String.t() | nil) :: Enumerable.t() def export(address_hash, from_period, to_period, filter_type \\ nil, filter_value \\ nil) do {from_block, to_block} = Helper.block_from_period(from_period, to_period) exchange_rate = Market.get_coin_exchange_rate() @@ -35,7 +29,8 @@ defmodule Explorer.Chain.CSVExport.AddressTransactionCsvExporter do # sobelow_skip ["DOS.StringToAtom"] def fetch_transactions(address_hash, from_block, to_block, filter_type, filter_value, paging_options) do options = - @necessity_by_association + [] + |> DenormalizationHelper.extend_block_necessity(:required) |> Keyword.put(:paging_options, paging_options) |> Keyword.put(:from_block, from_block) |> Keyword.put(:to_block, to_block) @@ -44,7 +39,7 @@ defmodule Explorer.Chain.CSVExport.AddressTransactionCsvExporter do else: &1 )).() - Chain.address_to_transactions_without_rewards(address_hash, options) + Transaction.address_to_transactions_without_rewards(address_hash, options) end defp to_csv_format(transactions, address_hash, exchange_rate) do @@ -67,7 +62,7 @@ defmodule Explorer.Chain.CSVExport.AddressTransactionCsvExporter do date_to_prices = Enum.reduce(transactions, %{}, fn tx, acc -> - date = DateTime.to_date(tx.block.timestamp) + date = tx |> Transaction.block_timestamp() |> DateTime.to_date() if Map.has_key?(acc, date) do acc @@ -79,12 +74,12 @@ defmodule Explorer.Chain.CSVExport.AddressTransactionCsvExporter do transaction_lists = transactions |> Stream.map(fn transaction -> - {opening_price, closing_price} = date_to_prices[DateTime.to_date(transaction.block.timestamp)] + {opening_price, closing_price} = date_to_prices[DateTime.to_date(Transaction.block_timestamp(transaction))] [ to_string(transaction.hash), transaction.block_number, - transaction.block.timestamp, + Transaction.block_timestamp(transaction), Address.checksum(transaction.from_address_hash), Address.checksum(transaction.to_address_hash), Address.checksum(transaction.created_contract_address_hash), diff --git a/apps/explorer/lib/explorer/chain/denormalization_helper.ex b/apps/explorer/lib/explorer/chain/denormalization_helper.ex new file mode 100644 index 000000000000..0199fc7359e1 --- /dev/null +++ b/apps/explorer/lib/explorer/chain/denormalization_helper.ex @@ -0,0 +1,50 @@ +defmodule Explorer.Chain.DenormalizationHelper do + @moduledoc """ + Helper functions for dynamic logic based on denormalization migration completeness + """ + + alias Explorer.Chain.Cache.BackgroundMigrations + + @spec extend_block_necessity(keyword(), :optional | :required) :: keyword() + def extend_block_necessity(opts, necessity \\ :optional) do + if denormalization_finished?() do + opts + else + Keyword.update(opts, :necessity_by_association, %{:block => necessity}, &Map.put(&1, :block, necessity)) + end + end + + @spec extend_transaction_block_necessity(keyword(), :optional | :required) :: keyword() + def extend_transaction_block_necessity(opts, necessity \\ :optional) do + if denormalization_finished?() do + opts + else + Keyword.update( + opts, + :necessity_by_association, + %{[transaction: :block] => necessity}, + &(&1 |> Map.delete(:transaction) |> Map.put([transaction: :block], necessity)) + ) + end + end + + @spec extend_transaction_preload(list()) :: list() + def extend_transaction_preload(preloads) do + if denormalization_finished?() do + preloads + else + [transaction: :block] ++ (preloads -- [:transaction]) + end + end + + @spec extend_block_preload(list()) :: list() + def extend_block_preload(preloads) do + if denormalization_finished?() do + preloads + else + [:block | preloads] + end + end + + def denormalization_finished?, do: BackgroundMigrations.get_denormalization_finished() +end diff --git a/apps/explorer/lib/explorer/chain/import/runner/blocks.ex b/apps/explorer/lib/explorer/chain/import/runner/blocks.ex index defc998b866d..4ed9d436d99e 100644 --- a/apps/explorer/lib/explorer/chain/import/runner/blocks.ex +++ b/apps/explorer/lib/explorer/chain/import/runner/blocks.ex @@ -9,6 +9,8 @@ defmodule Explorer.Chain.Import.Runner.Blocks do alias Ecto.{Changeset, Multi, Repo} + alias EthereumJSONRPC.Utility.RangesHelper + alias Explorer.Chain.{ Address, Block, @@ -62,7 +64,7 @@ defmodule Explorer.Chain.Import.Runner.Blocks do items_for_pending_ops = changes_list - |> filter_by_height_range(&is_block_in_range?(&1.number)) + |> filter_by_height_range(&RangesHelper.traceable_block_number?(&1.number)) |> Enum.filter(& &1.consensus) |> Enum.map(&{&1.number, &1.hash}) @@ -72,7 +74,8 @@ defmodule Explorer.Chain.Import.Runner.Blocks do run_func = fn repo -> {:ok, nonconsensus_items} = lose_consensus(repo, hashes, consensus_block_numbers, changes_list, insert_options) - {:ok, filter_by_height_range(nonconsensus_items, fn {number, _hash} -> is_block_in_range?(number) end)} + {:ok, + filter_by_height_range(nonconsensus_items, fn {number, _hash} -> RangesHelper.traceable_block_number?(number) end)} end multi @@ -214,12 +217,6 @@ defmodule Explorer.Chain.Import.Runner.Blocks do @impl Runner def timeout, do: @timeout - defp is_block_in_range?(number) do - minimal_block_height = Application.get_env(:indexer, :trace_first_block) - maximal_block_height = Application.get_env(:indexer, :trace_last_block) - number >= minimal_block_height && if(maximal_block_height, do: number <= maximal_block_height, else: true) - end - defp fork_transactions(%{ repo: repo, timeout: timeout, @@ -398,6 +395,18 @@ defmodule Explorer.Chain.Import.Runner.Blocks do timeout: timeout ) + repo.update_all( + from( + transaction in Transaction, + join: s in subquery(acquire_query), + on: transaction.block_hash == s.hash, + # we don't want to remove consensus from blocks that will be upserted + where: transaction.block_hash not in ^hashes + ), + [set: [block_consensus: false, updated_at: updated_at]], + timeout: timeout + ) + removed_consensus_block_hashes |> Enum.map(fn {number, _hash} -> number end) |> Enum.reject(&Enum.member?(consensus_block_numbers, &1)) @@ -890,10 +899,7 @@ defmodule Explorer.Chain.Import.Runner.Blocks do end defp filter_by_height_range(blocks, filter_func) do - minimal_block_height = Application.get_env(:indexer, :trace_first_block) - maximal_block_height = Application.get_env(:indexer, :trace_last_block) - - if minimal_block_height > 0 || maximal_block_height do + if RangesHelper.trace_ranges_present?() do Enum.filter(blocks, &filter_func.(&1)) else blocks diff --git a/apps/explorer/lib/explorer/chain/import/runner/internal_transactions.ex b/apps/explorer/lib/explorer/chain/import/runner/internal_transactions.ex index 0763d86ebdf2..a4d512e602b8 100644 --- a/apps/explorer/lib/explorer/chain/import/runner/internal_transactions.ex +++ b/apps/explorer/lib/explorer/chain/import/runner/internal_transactions.ex @@ -8,6 +8,7 @@ defmodule Explorer.Chain.Import.Runner.InternalTransactions do alias Ecto.Adapters.SQL alias Ecto.{Changeset, Multi, Repo} + alias EthereumJSONRPC.Utility.RangesHelper alias Explorer.Chain.{Block, Hash, Import, InternalTransaction, PendingBlockOperation, Transaction} alias Explorer.Chain.Events.Publisher alias Explorer.Chain.Import.Runner @@ -15,7 +16,7 @@ defmodule Explorer.Chain.Import.Runner.InternalTransactions do alias Explorer.Repo, as: ExplorerRepo alias Explorer.Utility.MissingRangesManipulator - import Ecto.Query, only: [from: 2, where: 3] + import Ecto.Query @behaviour Runner @@ -691,25 +692,29 @@ defmodule Explorer.Chain.Import.Runner.InternalTransactions do end defp remove_consensus_of_invalid_blocks(repo, invalid_block_numbers) do - minimal_block = Application.get_env(:indexer, :trace_first_block) - maximal_block = Application.get_env(:indexer, :trace_last_block) - if Enum.count(invalid_block_numbers) > 0 do - update_query = + update_block_query = from( block in Block, where: block.number in ^invalid_block_numbers and block.consensus == true, - where: block.number > ^minimal_block, + where: ^traceable_blocks_dynamic_query(), select: block.hash, # ShareLocks order already enforced by `acquire_blocks` (see docs: sharelocks.md) update: [set: [consensus: false]] ) - update_query = - if maximal_block, do: update_query |> where([block], block.number < ^maximal_block), else: update_query + update_transaction_query = + from( + transaction in Transaction, + where: transaction.block_number in ^invalid_block_numbers and transaction.block_consensus, + where: ^traceable_transactions_dynamic_query(), + # ShareLocks order already enforced by `acquire_blocks` (see docs: sharelocks.md) + update: [set: [block_consensus: false]] + ) try do - {_num, result} = repo.update_all(update_query, []) + {_num, result} = repo.update_all(update_block_query, []) + {_num, _result} = repo.update_all(update_transaction_query, []) MissingRangesManipulator.add_ranges_by_block_numbers(invalid_block_numbers) @@ -754,4 +759,30 @@ defmodule Explorer.Chain.Import.Runner.InternalTransactions do {:error, %{exception: postgrex_error, pending_hashes: valid_block_hashes}} end end + + defp traceable_blocks_dynamic_query do + if RangesHelper.trace_ranges_present?() do + block_ranges = RangesHelper.get_trace_block_ranges() + + Enum.reduce(block_ranges, dynamic([_], false), fn + _from.._to = range, acc -> dynamic([block], ^acc or block.number in ^range) + num_to_latest, acc -> dynamic([block], ^acc or block.number >= ^num_to_latest) + end) + else + dynamic([_], true) + end + end + + defp traceable_transactions_dynamic_query do + if RangesHelper.trace_ranges_present?() do + block_ranges = RangesHelper.get_trace_block_ranges() + + Enum.reduce(block_ranges, dynamic([_], false), fn + _from.._to = range, acc -> dynamic([transaction], ^acc or transaction.block_number in ^range) + num_to_latest, acc -> dynamic([transaction], ^acc or transaction.block_number >= ^num_to_latest) + end) + else + dynamic([_], true) + end + end end diff --git a/apps/explorer/lib/explorer/chain/import/runner/logs.ex b/apps/explorer/lib/explorer/chain/import/runner/logs.ex index c90adaa04a41..7c0e591a0f92 100644 --- a/apps/explorer/lib/explorer/chain/import/runner/logs.ex +++ b/apps/explorer/lib/explorer/chain/import/runner/logs.ex @@ -92,7 +92,6 @@ defmodule Explorer.Chain.Import.Runner.Logs do third_topic: fragment("EXCLUDED.third_topic"), fourth_topic: fragment("EXCLUDED.fourth_topic"), # Don't update `index` as it is part of the composite primary key and used for the conflict target - type: fragment("EXCLUDED.type"), # Don't update `transaction_hash` as it is part of the composite primary key and used for the conflict target inserted_at: fragment("LEAST(?, EXCLUDED.inserted_at)", log.inserted_at), updated_at: fragment("GREATEST(?, EXCLUDED.updated_at)", log.updated_at) @@ -100,14 +99,13 @@ defmodule Explorer.Chain.Import.Runner.Logs do ], where: fragment( - "(EXCLUDED.address_hash, EXCLUDED.data, EXCLUDED.first_topic, EXCLUDED.second_topic, EXCLUDED.third_topic, EXCLUDED.fourth_topic, EXCLUDED.type) IS DISTINCT FROM (?, ?, ?, ?, ?, ?, ?)", + "(EXCLUDED.address_hash, EXCLUDED.data, EXCLUDED.first_topic, EXCLUDED.second_topic, EXCLUDED.third_topic, EXCLUDED.fourth_topic) IS DISTINCT FROM (?, ?, ?, ?, ?, ?)", log.address_hash, log.data, log.first_topic, log.second_topic, log.third_topic, - log.fourth_topic, - log.type + log.fourth_topic ) ) end diff --git a/apps/explorer/lib/explorer/chain/import/runner/transactions.ex b/apps/explorer/lib/explorer/chain/import/runner/transactions.ex index 077bc41a286d..62578969a783 100644 --- a/apps/explorer/lib/explorer/chain/import/runner/transactions.ex +++ b/apps/explorer/lib/explorer/chain/import/runner/transactions.ex @@ -116,6 +116,8 @@ defmodule Explorer.Chain.Import.Runner.Transactions do block_hash: fragment("EXCLUDED.block_hash"), old_block_hash: transaction.block_hash, block_number: fragment("EXCLUDED.block_number"), + block_consensus: fragment("EXCLUDED.block_consensus"), + block_timestamp: fragment("EXCLUDED.block_timestamp"), created_contract_address_hash: fragment("EXCLUDED.created_contract_address_hash"), created_contract_code_indexed_at: fragment("EXCLUDED.created_contract_code_indexed_at"), cumulative_gas_used: fragment("EXCLUDED.cumulative_gas_used"), @@ -159,9 +161,11 @@ defmodule Explorer.Chain.Import.Runner.Transactions do ], where: fragment( - "(EXCLUDED.block_hash, EXCLUDED.block_number, EXCLUDED.created_contract_address_hash, EXCLUDED.created_contract_code_indexed_at, EXCLUDED.cumulative_gas_used, EXCLUDED.from_address_hash, EXCLUDED.gas, EXCLUDED.gas_price, EXCLUDED.gas_used, EXCLUDED.index, EXCLUDED.input, EXCLUDED.nonce, EXCLUDED.r, EXCLUDED.s, EXCLUDED.status, EXCLUDED.to_address_hash, EXCLUDED.v, EXCLUDED.value, EXCLUDED.earliest_processing_start, EXCLUDED.revert_reason, EXCLUDED.max_priority_fee_per_gas, EXCLUDED.max_fee_per_gas, EXCLUDED.type, EXCLUDED.execution_node_hash, EXCLUDED.wrapped_type, EXCLUDED.wrapped_nonce, EXCLUDED.wrapped_to_address_hash, EXCLUDED.wrapped_gas, EXCLUDED.wrapped_gas_price, EXCLUDED.wrapped_max_priority_fee_per_gas, EXCLUDED.wrapped_max_fee_per_gas, EXCLUDED.wrapped_value, EXCLUDED.wrapped_input, EXCLUDED.wrapped_v, EXCLUDED.wrapped_r, EXCLUDED.wrapped_s, EXCLUDED.wrapped_hash) IS DISTINCT FROM (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", + "(EXCLUDED.block_hash, EXCLUDED.block_number, EXCLUDED.block_consensus, EXCLUDED.block_timestamp, EXCLUDED.created_contract_address_hash, EXCLUDED.created_contract_code_indexed_at, EXCLUDED.cumulative_gas_used, EXCLUDED.from_address_hash, EXCLUDED.gas, EXCLUDED.gas_price, EXCLUDED.gas_used, EXCLUDED.index, EXCLUDED.input, EXCLUDED.nonce, EXCLUDED.r, EXCLUDED.s, EXCLUDED.status, EXCLUDED.to_address_hash, EXCLUDED.v, EXCLUDED.value, EXCLUDED.earliest_processing_start, EXCLUDED.revert_reason, EXCLUDED.max_priority_fee_per_gas, EXCLUDED.max_fee_per_gas, EXCLUDED.type, EXCLUDED.execution_node_hash, EXCLUDED.wrapped_type, EXCLUDED.wrapped_nonce, EXCLUDED.wrapped_to_address_hash, EXCLUDED.wrapped_gas, EXCLUDED.wrapped_gas_price, EXCLUDED.wrapped_max_priority_fee_per_gas, EXCLUDED.wrapped_max_fee_per_gas, EXCLUDED.wrapped_value, EXCLUDED.wrapped_input, EXCLUDED.wrapped_v, EXCLUDED.wrapped_r, EXCLUDED.wrapped_s, EXCLUDED.wrapped_hash) IS DISTINCT FROM (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", transaction.block_hash, transaction.block_number, + transaction.block_consensus, + transaction.block_timestamp, transaction.created_contract_address_hash, transaction.created_contract_code_indexed_at, transaction.cumulative_gas_used, @@ -207,6 +211,8 @@ defmodule Explorer.Chain.Import.Runner.Transactions do block_hash: fragment("EXCLUDED.block_hash"), old_block_hash: transaction.block_hash, block_number: fragment("EXCLUDED.block_number"), + block_consensus: fragment("EXCLUDED.block_consensus"), + block_timestamp: fragment("EXCLUDED.block_timestamp"), created_contract_address_hash: fragment("EXCLUDED.created_contract_address_hash"), created_contract_code_indexed_at: fragment("EXCLUDED.created_contract_code_indexed_at"), cumulative_gas_used: fragment("EXCLUDED.cumulative_gas_used"), @@ -236,9 +242,11 @@ defmodule Explorer.Chain.Import.Runner.Transactions do ], where: fragment( - "(EXCLUDED.block_hash, EXCLUDED.block_number, EXCLUDED.created_contract_address_hash, EXCLUDED.created_contract_code_indexed_at, EXCLUDED.cumulative_gas_used, EXCLUDED.from_address_hash, EXCLUDED.gas, EXCLUDED.gas_price, EXCLUDED.gas_used, EXCLUDED.index, EXCLUDED.input, EXCLUDED.nonce, EXCLUDED.r, EXCLUDED.s, EXCLUDED.status, EXCLUDED.to_address_hash, EXCLUDED.v, EXCLUDED.value, EXCLUDED.earliest_processing_start, EXCLUDED.revert_reason, EXCLUDED.max_priority_fee_per_gas, EXCLUDED.max_fee_per_gas, EXCLUDED.type) IS DISTINCT FROM (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", + "(EXCLUDED.block_hash, EXCLUDED.block_number, EXCLUDED.block_consensus, EXCLUDED.block_timestamp, EXCLUDED.created_contract_address_hash, EXCLUDED.created_contract_code_indexed_at, EXCLUDED.cumulative_gas_used, EXCLUDED.from_address_hash, EXCLUDED.gas, EXCLUDED.gas_price, EXCLUDED.gas_used, EXCLUDED.index, EXCLUDED.input, EXCLUDED.nonce, EXCLUDED.r, EXCLUDED.s, EXCLUDED.status, EXCLUDED.to_address_hash, EXCLUDED.v, EXCLUDED.value, EXCLUDED.earliest_processing_start, EXCLUDED.revert_reason, EXCLUDED.max_priority_fee_per_gas, EXCLUDED.max_fee_per_gas, EXCLUDED.type) IS DISTINCT FROM (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", transaction.block_hash, transaction.block_number, + transaction.block_consensus, + transaction.block_timestamp, transaction.created_contract_address_hash, transaction.created_contract_code_indexed_at, transaction.cumulative_gas_used, @@ -291,14 +299,20 @@ defmodule Explorer.Chain.Import.Runner.Transactions do ), on: transaction.hash == new_transaction.hash, where: transaction.block_hash != new_transaction.block_hash, - select: transaction.block_hash + select: %{hash: transaction.hash, block_hash: transaction.block_hash} ) block_hashes = blocks_with_recollated_transactions |> repo.all() + |> Enum.map(fn %{block_hash: block_hash} -> block_hash end) |> Enum.uniq() + transaction_hashes = + blocks_with_recollated_transactions + |> repo.all() + |> Enum.map(fn %{hash: hash} -> hash end) + if Enum.empty?(block_hashes) do {:ok, []} else @@ -357,5 +371,32 @@ defmodule Explorer.Chain.Import.Runner.Transactions do {:error, %{exception: postgrex_error, block_hashes: block_hashes}} end end + + if Enum.empty?(transaction_hashes) do + {:ok, []} + else + query = + from( + transaction in Transaction, + where: transaction.hash in ^transaction_hashes, + # Enforce Block ShareLocks order (see docs: sharelocks.md) + order_by: [asc: transaction.hash], + lock: "FOR UPDATE" + ) + + try do + {_, result} = + repo.update_all( + from(transaction in Transaction, join: s in subquery(query), on: transaction.hash == s.hash), + [set: [block_consensus: false, updated_at: updated_at]], + timeout: timeout + ) + + {:ok, result} + rescue + postgrex_error in Postgrex.Error -> + {:error, %{exception: postgrex_error, transaction_hashes: transaction_hashes}} + end + end end end diff --git a/apps/explorer/lib/explorer/chain/internal_transaction.ex b/apps/explorer/lib/explorer/chain/internal_transaction.ex index c86bfa699f53..82ce66e8c3dc 100644 --- a/apps/explorer/lib/explorer/chain/internal_transaction.ex +++ b/apps/explorer/lib/explorer/chain/internal_transaction.ex @@ -580,38 +580,6 @@ defmodule Explorer.Chain.InternalTransaction do ) end - def where_block_number_in_period(query, from_number, to_number) when is_nil(from_number) and not is_nil(to_number) do - where( - query, - [it], - it.block_number <= ^to_number - ) - end - - def where_block_number_in_period(query, from_number, to_number) when not is_nil(from_number) and is_nil(to_number) do - where( - query, - [it], - it.block_number > ^from_number - ) - end - - def where_block_number_in_period(query, from_number, to_number) when is_nil(from_number) and is_nil(to_number) do - where( - query, - [it], - 1 - ) - end - - def where_block_number_in_period(query, from_number, to_number) do - where( - query, - [it], - it.block_number > ^from_number and it.block_number <= ^to_number - ) - end - def where_block_number_is_not_null(query) do where(query, [t], not is_nil(t.block_number)) end diff --git a/apps/explorer/lib/explorer/chain/log.ex b/apps/explorer/lib/explorer/chain/log.ex index 3b04958fdcb7..be27eead7641 100644 --- a/apps/explorer/lib/explorer/chain/log.ex +++ b/apps/explorer/lib/explorer/chain/log.ex @@ -7,12 +7,12 @@ defmodule Explorer.Chain.Log do alias ABI.{Event, FunctionSelector} alias Explorer.Chain - alias Explorer.Chain.{Address, Block, ContractMethod, Data, Hash, Transaction} + alias Explorer.Chain.{Address, Block, ContractMethod, Data, Hash, Log, Transaction} alias Explorer.Chain.SmartContract.Proxy alias Explorer.SmartContract.SigProviderInterface @required_attrs ~w(address_hash data block_hash index transaction_hash)a - @optional_attrs ~w(first_topic second_topic third_topic fourth_topic type block_number)a + @optional_attrs ~w(first_topic second_topic third_topic fourth_topic block_number)a @typedoc """ * `address` - address of contract that generate the event @@ -27,7 +27,6 @@ defmodule Explorer.Chain.Log do * `transaction` - transaction for which `log` is * `transaction_hash` - foreign key for `transaction`. * `index` - index of the log entry in all logs for the `transaction` - * `type` - type of event. *Nethermind-only* """ @type t :: %__MODULE__{ address: %Ecto.Association.NotLoaded{} | Address.t(), @@ -35,25 +34,23 @@ defmodule Explorer.Chain.Log do block_hash: Hash.Full.t(), block_number: non_neg_integer() | nil, data: Data.t(), - first_topic: String.t(), - second_topic: String.t(), - third_topic: String.t(), - fourth_topic: String.t(), + first_topic: Hash.Full.t(), + second_topic: Hash.Full.t(), + third_topic: Hash.Full.t(), + fourth_topic: Hash.Full.t(), transaction: %Ecto.Association.NotLoaded{} | Transaction.t(), transaction_hash: Hash.Full.t(), - index: non_neg_integer(), - type: String.t() | nil + index: non_neg_integer() } @primary_key false schema "logs" do field(:data, Data) - field(:first_topic, :string) - field(:second_topic, :string) - field(:third_topic, :string) - field(:fourth_topic, :string) + field(:first_topic, Hash.Full) + field(:second_topic, Hash.Full) + field(:third_topic, Hash.Full) + field(:fourth_topic, Hash.Full) field(:index, :integer, primary_key: true) - field(:type, :string) field(:block_number, :integer) timestamps() @@ -76,8 +73,7 @@ defmodule Explorer.Chain.Log do end @doc """ - `address_hash` and `transaction_hash` are converted to `t:Explorer.Chain.Hash.t/0`. The allowed values for `type` - are currently unknown, so it is left as a `t:String.t/0`. + `address_hash` and `transaction_hash` are converted to `t:Explorer.Chain.Hash.t/0`. iex> changeset = Explorer.Chain.Log.changeset( ...> %Explorer.Chain.Log{}, @@ -90,8 +86,7 @@ defmodule Explorer.Chain.Log do ...> index: 0, ...> second_topic: nil, ...> third_topic: nil, - ...> transaction_hash: "0x53bd884872de3e488692881baeec262e7b95234d3965248c39fe992fffd433e5", - ...> type: "mined" + ...> transaction_hash: "0x53bd884872de3e488692881baeec262e7b95234d3965248c39fe992fffd433e5" ...> } ...> ) iex> changeset.valid? @@ -107,8 +102,6 @@ defmodule Explorer.Chain.Log do bytes: <<83, 189, 136, 72, 114, 222, 62, 72, 134, 146, 136, 27, 174, 236, 38, 46, 123, 149, 35, 77, 57, 101, 36, 140, 57, 254, 153, 47, 255, 212, 51, 229>> } - iex> changeset.changes.type - "mined" """ def changeset(%__MODULE__{} = log, attrs \\ %{}) do @@ -121,33 +114,47 @@ defmodule Explorer.Chain.Log do @doc """ Decode transaction log data. """ - + @spec decode(Log.t(), Transaction.t(), any(), boolean, map(), map()) :: + {{:ok, String.t(), String.t(), map()} + | {:error, atom()} + | {:error, atom(), list()} + | {{:error, :contract_not_verified, list()}, any()}, map(), map()} def decode(log, transaction, options, skip_sig_provider?, contracts_acc \\ %{}, events_acc \\ %{}) do - case check_cache(contracts_acc, log.address_hash, options) do - {nil, contracts_acc} -> - {result, events_acc} = find_candidates(log, transaction, options, events_acc) - {result, contracts_acc, events_acc} - - {full_abi, contracts_acc} -> - with {:ok, selector, mapping} <- find_and_decode(full_abi, log, transaction), - identifier <- Base.encode16(selector.method_id, case: :lower), - text <- function_call(selector.function, mapping) do - {{:ok, identifier, text, mapping}, contracts_acc, events_acc} - else - {:error, :could_not_decode} -> - case find_candidates(log, transaction, options, events_acc) do - {{:error, :contract_not_verified, []}, events_acc} -> - {decode_event_via_sig_provider(log, transaction, false, skip_sig_provider?), contracts_acc, events_acc} + with {full_abi, contracts_acc} <- check_cache(contracts_acc, log.address_hash, options), + {:no_abi, false} <- {:no_abi, is_nil(full_abi)}, + {:ok, selector, mapping} <- find_and_decode(full_abi, log, transaction.hash), + identifier <- Base.encode16(selector.method_id, case: :lower), + text <- function_call(selector.function, mapping) do + {{:ok, identifier, text, mapping}, contracts_acc, events_acc} + else + {:error, _} = error -> + handle_method_decode_error(error, log, transaction, options, skip_sig_provider?, contracts_acc, events_acc) + + {:no_abi, true} -> + handle_method_decode_error( + {:error, :could_not_decode}, + log, + transaction, + options, + skip_sig_provider?, + contracts_acc, + events_acc + ) + end + end - {{:error, :contract_not_verified, candidates}, events_acc} -> - {{:error, :contract_verified, candidates}, contracts_acc, events_acc} + defp handle_method_decode_error(error, log, transaction, options, skip_sig_provider?, contracts_acc, events_acc) do + case error do + {:error, _reason} -> + case find_method_candidates(log, transaction, options, events_acc, skip_sig_provider?) do + {{:error, :contract_not_verified, []}, events_acc} -> + {decode_event_via_sig_provider(log, transaction, false, skip_sig_provider?), contracts_acc, events_acc} - {_, events_acc} -> - {decode_event_via_sig_provider(log, transaction, false, skip_sig_provider?), contracts_acc, events_acc} - end + {{:error, :contract_not_verified, candidates}, events_acc} -> + {{:error, :contract_not_verified, candidates}, contracts_acc, events_acc} - {:error, reason} -> - {{:error, reason}, contracts_acc, events_acc} + {_, events_acc} -> + {decode_event_via_sig_provider(log, transaction, false, skip_sig_provider?), contracts_acc, events_acc} end end end @@ -175,36 +182,29 @@ defmodule Explorer.Chain.Log do end end - defp find_candidates(log, transaction, options, events_acc) do - case log.first_topic do - "0x" <> hex_part -> - case Integer.parse(hex_part, 16) do - {number, ""} -> - <> = :binary.encode_unsigned(number) - check_events_cache(events_acc, method_id, log, transaction, options) - - _ -> - {{:error, :could_not_decode}, events_acc} - end + defp find_method_candidates(log, transaction, options, events_acc, skip_sig_provider?) do + if is_nil(log.first_topic) do + {{:error, :could_not_decode}, events_acc} + else + <> = log.first_topic.bytes - _ -> - {{:error, :could_not_decode}, events_acc} + if Map.has_key?(events_acc, method_id) do + {events_acc[method_id], events_acc} + else + result = find_method_candidates_from_db(method_id, log, transaction, options, skip_sig_provider?) + {result, Map.put(events_acc, method_id, result)} + end end end - defp find_candidates_query(method_id, log, transaction, options) do - candidates_query = - from( - contract_method in ContractMethod, - where: contract_method.identifier == ^method_id, - limit: 3 - ) + defp find_method_candidates_from_db(method_id, log, transaction, options, skip_sig_provider?) do + candidates_query = ContractMethod.find_contract_method_query(method_id, 3) candidates = candidates_query |> Chain.select_repo(options).all() |> Enum.flat_map(fn contract_method -> - case find_and_decode([contract_method.abi], log, transaction) do + case find_and_decode([contract_method.abi], log, transaction.hash) do {:ok, selector, mapping} -> identifier = Base.encode16(selector.method_id, case: :lower) text = function_call(selector.function, mapping) @@ -218,29 +218,27 @@ defmodule Explorer.Chain.Log do |> Enum.take(1) {:error, :contract_not_verified, - if(candidates == [], do: decode_event_via_sig_provider(log, transaction, true), else: candidates)} + if(candidates == [], + do: + if(skip_sig_provider?, + do: [], + else: decode_event_via_sig_provider(log, transaction, true) + ), + else: candidates + )} end - defp check_events_cache(events_acc, method_id, log, transaction, options) do - if Map.has_key?(events_acc, method_id) do - {events_acc[method_id], events_acc} - else - result = find_candidates_query(method_id, log, transaction, options) - {result, Map.put(events_acc, method_id, result)} - end - end - - @spec find_and_decode([map()], __MODULE__.t(), Transaction.t()) :: + @spec find_and_decode([map()], __MODULE__.t(), Hash.t()) :: {:error, any} | {:ok, ABI.FunctionSelector.t(), any} - def find_and_decode(abi, log, transaction) do + def find_and_decode(abi, log, transaction_hash) do with {%FunctionSelector{} = selector, mapping} <- abi |> ABI.parse_specification(include_events?: true) |> Event.find_and_decode( - decode16!(log.first_topic), - decode16!(log.second_topic), - decode16!(log.third_topic), - decode16!(log.fourth_topic), + log.first_topic && log.first_topic.bytes, + log.second_topic && log.second_topic.bytes, + log.third_topic && log.third_topic.bytes, + log.fourth_topic && log.fourth_topic.bytes, log.data.bytes ) do {:ok, selector, mapping} @@ -249,8 +247,8 @@ defmodule Explorer.Chain.Log do e -> Logger.warn(fn -> [ - "Could not decode input data for log from transaction: ", - Hash.to_iodata(transaction.hash), + "Could not decode input data for log from transaction hash: ", + Hash.to_iodata(transaction_hash), Exception.format(:error, e, __STACKTRACE__) ] end) @@ -262,12 +260,7 @@ defmodule Explorer.Chain.Log do text = mapping |> Stream.map(fn {name, type, indexed?, _value} -> - indexed_keyword = - if indexed? do - ["indexed "] - else - [] - end + indexed_keyword = if indexed?, do: ["indexed "], else: [] [type, " ", indexed_keyword, name] end) @@ -276,7 +269,12 @@ defmodule Explorer.Chain.Log do IO.iodata_to_binary([name, "(", text, ")"]) end - defp decode_event_via_sig_provider(log, transaction, only_candidates?, skip_sig_provider? \\ false) do + defp decode_event_via_sig_provider( + log, + transaction, + only_candidates?, + skip_sig_provider? \\ false + ) do with true <- SigProviderInterface.enabled?(), false <- skip_sig_provider?, {:ok, result} <- @@ -292,7 +290,7 @@ defmodule Explorer.Chain.Log do true <- is_list(result), false <- Enum.empty?(result), abi <- [result |> List.first() |> Map.put("type", "event")], - {:ok, selector, mapping} <- find_and_decode(abi, log, transaction), + {:ok, selector, mapping} <- find_and_decode(abi, log, transaction.hash), identifier <- Base.encode16(selector.method_id, case: :lower), text <- function_call(selector.function, mapping) do if only_candidates? do diff --git a/apps/explorer/lib/explorer/chain/search.ex b/apps/explorer/lib/explorer/chain/search.ex index 64e8c1640645..20082bccbfb8 100644 --- a/apps/explorer/lib/explorer/chain/search.ex +++ b/apps/explorer/lib/explorer/chain/search.ex @@ -14,13 +14,14 @@ defmodule Explorer.Chain.Search do ] import Explorer.Chain, only: [select_repo: 1] - + import Explorer.MicroserviceInterfaces.BENS, only: [ens_domain_name_lookup: 1] alias Explorer.{Chain, PagingOptions} alias Explorer.Tags.{AddressTag, AddressToTag} alias Explorer.Chain.{ Address, Block, + DenormalizationHelper, SmartContract, Token, Transaction @@ -33,74 +34,87 @@ defmodule Explorer.Chain.Search do def joint_search(paging_options, offset, raw_string, options \\ []) do string = String.trim(raw_string) - case prepare_search_term(string) do - {:some, term} -> - tokens_query = search_token_query(string, term) - contracts_query = search_contract_query(term) - labels_query = search_label_query(term) - tx_query = search_tx_query(string) - address_query = search_address_query(string) - block_query = search_block_query(string) - - basic_query = - from( - tokens in subquery(tokens_query), - union: ^contracts_query, - union: ^labels_query - ) + ens_task = maybe_run_ens_task(paging_options, raw_string, options) + + result = + case prepare_search_term(string) do + {:some, term} -> + tokens_query = search_token_query(string, term) + contracts_query = search_contract_query(term) + labels_query = search_label_query(term) + tx_query = search_tx_query(string) + address_query = search_address_query(string) + block_query = search_block_query(string) + + basic_query = + from( + tokens in subquery(tokens_query), + union: ^contracts_query, + union: ^labels_query + ) - query = - cond do - address_query -> - basic_query - |> union(^address_query) + query = + cond do + address_query -> + basic_query + |> union(^address_query) + + tx_query -> + basic_query + |> union(^tx_query) + |> union(^block_query) + + block_query -> + basic_query + |> union(^block_query) + + true -> + basic_query + end + + ordered_query = + from(items in subquery(query), + order_by: [ + desc: items.priority, + desc_nulls_last: items.circulating_market_cap, + desc_nulls_last: items.exchange_rate, + desc_nulls_last: items.is_verified_via_admin_panel, + desc_nulls_last: items.holder_count, + asc: items.name, + desc: items.inserted_at + ], + limit: ^paging_options.page_size, + offset: ^offset + ) - tx_query -> - basic_query - |> union(^tx_query) - |> union(^block_query) + paginated_ordered_query = + ordered_query + |> page_search_results(paging_options) - block_query -> - basic_query - |> union(^block_query) + search_results = select_repo(options).all(paginated_ordered_query) - true -> - basic_query - end + search_results + |> Enum.map(fn result -> + result + |> compose_result_checksummed_address_hash() + |> format_timestamp() + end) - ordered_query = - from(items in subquery(query), - order_by: [ - desc: items.priority, - desc_nulls_last: items.circulating_market_cap, - desc_nulls_last: items.exchange_rate, - desc_nulls_last: items.is_verified_via_admin_panel, - desc_nulls_last: items.holder_count, - asc: items.name, - desc: items.inserted_at - ], - limit: ^paging_options.page_size, - offset: ^offset - ) + _ -> + [] + end - paginated_ordered_query = - ordered_query - |> page_search_results(paging_options) + ens_result = (ens_task && await_ens_task(ens_task)) || [] - search_results = select_repo(options).all(paginated_ordered_query) - - search_results - |> Enum.map(fn result -> - result - |> compose_result_checksummed_address_hash() - |> format_timestamp() - end) + result ++ ens_result + end - _ -> - [] - end + defp maybe_run_ens_task(%PagingOptions{key: nil}, query_string, options) do + Task.async(fn -> search_ens_name(query_string, options) end) end + defp maybe_run_ens_task(_, _query_string, _options), do: nil + @doc """ Search function. Differences from joint_search/4: 1. Returns all the found categories (amount of results up to `paging_options.page_size`). @@ -111,6 +125,7 @@ defmodule Explorer.Chain.Search do @spec balanced_unpaginated_search(PagingOptions.t(), binary(), [Chain.api?()] | []) :: list def balanced_unpaginated_search(paging_options, raw_search_query, options \\ []) do search_query = String.trim(raw_search_query) + ens_task = Task.async(fn -> search_ens_name(raw_search_query, options) end) case prepare_search_term(search_query) do {:some, term} -> @@ -167,8 +182,10 @@ defmodule Explorer.Chain.Search do [] end + ens_result = await_ens_task(ens_task) + non_empty_lists = - [tokens_result, contracts_result, labels_result, tx_result, address_result, blocks_result] + [tokens_result, contracts_result, labels_result, tx_result, address_result, blocks_result, ens_result] |> Enum.filter(fn list -> Enum.count(list) > 0 end) |> Enum.sort_by(fn list -> Enum.count(list) end, :asc) @@ -190,6 +207,16 @@ defmodule Explorer.Chain.Search do end end + defp await_ens_task(ens_task) do + case Task.yield(ens_task, 5000) || Task.shutdown(ens_task) do + {:ok, result} -> + result + + _ -> + [] + end + end + def prepare_search_term(string) do case Regex.scan(~r/[a-zA-Z0-9]+/, string) do [_ | _] = words -> @@ -356,38 +383,72 @@ defmodule Explorer.Chain.Search do end defp search_tx_query(term) do - case Chain.string_to_transaction_hash(term) do - {:ok, tx_hash} -> - transaction_search_fields = %{ - address_hash: dynamic([_, _], type(^nil, :binary)), - tx_hash: dynamic([transaction, _], transaction.hash), - block_hash: dynamic([_, _], type(^nil, :binary)), - type: "transaction", - name: nil, - symbol: nil, - holder_count: nil, - inserted_at: dynamic([transaction, _], transaction.inserted_at), - block_number: 0, - icon_url: nil, - token_type: nil, - timestamp: dynamic([_, block], block.timestamp), - verified: nil, - exchange_rate: nil, - total_supply: nil, - circulating_market_cap: nil, - priority: 0, - is_verified_via_admin_panel: nil - } + if DenormalizationHelper.denormalization_finished?() do + case Chain.string_to_transaction_hash(term) do + {:ok, tx_hash} -> + transaction_search_fields = %{ + address_hash: dynamic([_], type(^nil, :binary)), + tx_hash: dynamic([transaction], transaction.hash), + block_hash: dynamic([_], type(^nil, :binary)), + type: "transaction", + name: nil, + symbol: nil, + holder_count: nil, + inserted_at: dynamic([transaction], transaction.inserted_at), + block_number: 0, + icon_url: nil, + token_type: nil, + timestamp: dynamic([transaction], transaction.block_timestamp), + verified: nil, + exchange_rate: nil, + total_supply: nil, + circulating_market_cap: nil, + priority: 0, + is_verified_via_admin_panel: nil + } + + from(transaction in Transaction, + where: transaction.hash == ^tx_hash, + select: ^transaction_search_fields + ) - from(transaction in Transaction, - left_join: block in Block, - on: transaction.block_hash == block.hash, - where: transaction.hash == ^tx_hash, - select: ^transaction_search_fields - ) + _ -> + nil + end + else + case Chain.string_to_transaction_hash(term) do + {:ok, tx_hash} -> + transaction_search_fields = %{ + address_hash: dynamic([_, _], type(^nil, :binary)), + tx_hash: dynamic([transaction, _], transaction.hash), + block_hash: dynamic([_, _], type(^nil, :binary)), + type: "transaction", + name: nil, + symbol: nil, + holder_count: nil, + inserted_at: dynamic([transaction, _], transaction.inserted_at), + block_number: 0, + icon_url: nil, + token_type: nil, + timestamp: dynamic([_, block], block.timestamp), + verified: nil, + exchange_rate: nil, + total_supply: nil, + circulating_market_cap: nil, + priority: 0, + is_verified_via_admin_panel: nil + } + + from(transaction in Transaction, + left_join: block in Block, + on: transaction.block_hash == block.hash, + where: transaction.hash == ^tx_hash, + select: ^transaction_search_fields + ) - _ -> - nil + _ -> + nil + end end end @@ -525,4 +586,49 @@ defmodule Explorer.Chain.Search do result end end + + defp search_ens_name(search_query, options) do + trimmed_query = String.trim(search_query) + + with true <- Regex.match?(~r/\w+\.\w+/, trimmed_query), + result when is_map(result) <- ens_domain_name_lookup(search_query) do + [ + result[:address_hash] + |> search_address_query() + |> select_repo(options).all() + |> merge_address_search_result_with_ens_info(result) + ] + else + _ -> + [] + end + end + + defp merge_address_search_result_with_ens_info([], ens_info) do + %{ + address_hash: ens_info[:address_hash], + block_hash: nil, + tx_hash: nil, + type: "address", + name: nil, + symbol: nil, + holder_count: nil, + inserted_at: nil, + block_number: 0, + icon_url: nil, + token_type: nil, + timestamp: nil, + verified: false, + exchange_rate: nil, + total_supply: nil, + circulating_market_cap: nil, + priority: 0, + is_verified_via_admin_panel: nil, + ens_info: ens_info + } + end + + defp merge_address_search_result_with_ens_info([address], ens_info) do + Map.put(address |> compose_result_checksummed_address_hash(), :ens_info, ens_info) + end end diff --git a/apps/explorer/lib/explorer/chain/smart_contract.ex b/apps/explorer/lib/explorer/chain/smart_contract.ex index edf9dda5441e..1d381ef92ec0 100644 --- a/apps/explorer/lib/explorer/chain/smart_contract.ex +++ b/apps/explorer/lib/explorer/chain/smart_contract.ex @@ -14,7 +14,7 @@ defmodule Explorer.Chain.SmartContract do alias Ecto.{Changeset, Multi} alias Explorer.Counters.AverageBlockTime - alias Explorer.{Chain, Repo} + alias Explorer.{Chain, Repo, SortingHelper} alias Explorer.Chain.{ Address, @@ -53,6 +53,8 @@ defmodule Explorer.Chain.SmartContract do @burn_address_hash_string end + @default_sorting [desc: :id] + @typedoc """ The name of a parameter to a function or event. """ @@ -278,7 +280,7 @@ defmodule Explorer.Chain.SmartContract do field(:constructor_arguments, :string) field(:evm_version, :string) field(:optimization_runs, :integer) - embeds_many(:external_libraries, ExternalLibrary) + embeds_many(:external_libraries, ExternalLibrary, on_replace: :delete) field(:abi, {:array, :map}) field(:verified_via_sourcify, :boolean) field(:partially_verified, :boolean) @@ -1242,4 +1244,56 @@ defmodule Explorer.Chain.SmartContract do if smart_contract, do: !smart_contract.partially_verified, else: false end + + @spec verified_contracts([ + Chain.paging_options() + | Chain.necessity_by_association_option() + | {:filter, :solidity | :vyper | :yul} + | {:search, String.t()} + | {:sorting, SortingHelper.sorting_params()} + | Chain.api?() + ]) :: [__MODULE__.t()] + def verified_contracts(options \\ []) do + paging_options = Keyword.get(options, :paging_options, Chain.default_paging_options()) + sorting_options = Keyword.get(options, :sorting, []) + necessity_by_association = Keyword.get(options, :necessity_by_association, %{}) + filter = Keyword.get(options, :filter, nil) + search_string = Keyword.get(options, :search, nil) + + query = from(contract in __MODULE__) + + query + |> filter_contracts(filter) + |> search_contracts(search_string) + |> SortingHelper.apply_sorting(sorting_options, @default_sorting) + |> SortingHelper.page_with_sorting(paging_options, sorting_options, @default_sorting) + |> Chain.join_associations(necessity_by_association) + |> Chain.select_repo(options).all() + end + + defp search_contracts(basic_query, nil), do: basic_query + + defp search_contracts(basic_query, search_string) do + from(contract in basic_query, + where: + ilike(contract.name, ^"%#{search_string}%") or + ilike(fragment("'0x' || encode(?, 'hex')", contract.address_hash), ^"%#{search_string}%") + ) + end + + defp filter_contracts(basic_query, :solidity) do + basic_query + |> where(is_vyper_contract: ^false) + end + + defp filter_contracts(basic_query, :vyper) do + basic_query + |> where(is_vyper_contract: ^true) + end + + defp filter_contracts(basic_query, :yul) do + from(query in basic_query, where: is_nil(query.abi)) + end + + defp filter_contracts(basic_query, _), do: basic_query end diff --git a/apps/explorer/lib/explorer/chain/smart_contract/proxy.ex b/apps/explorer/lib/explorer/chain/smart_contract/proxy.ex index 7c086516059a..cbd6dd4ee55f 100644 --- a/apps/explorer/lib/explorer/chain/smart_contract/proxy.ex +++ b/apps/explorer/lib/explorer/chain/smart_contract/proxy.ex @@ -13,13 +13,15 @@ defmodule Explorer.Chain.SmartContract.Proxy do string_to_address_hash: 1 ] - import Explorer.Chain.SmartContract, only: [is_burn_signature_or_nil: 1] + import Explorer.Chain.SmartContract, only: [burn_address_hash_string: 0, is_burn_signature_or_nil: 1] # supported signatures: # 5c60da1b = keccak256(implementation()) @implementation_signature "5c60da1b" # aaf10f42 = keccak256(getImplementation()) @get_implementation_signature "aaf10f42" + # bb82aa5e = keccak256(comptrollerImplementation()) Compound protocol proxy pattern + @comptroller_implementation_signature "bb82aa5e" # aaf10f42 = keccak256(getAddress(bytes32)) @get_address_signature "21f8a721" @@ -114,7 +116,8 @@ defmodule Explorer.Chain.SmartContract.Proxy do def gnosis_safe_contract?(abi) when is_nil(abi), do: false @doc """ - Checks if the input of the smart-contract follows master-copy proxy pattern + Checks if the input of the smart-contract follows master-copy (or Safe) proxy pattern before + fetching its implementation from 0x0 storage pointer """ @spec master_copy_pattern?(map()) :: any() def master_copy_pattern?(method) do @@ -155,10 +158,72 @@ defmodule Explorer.Chain.SmartContract.Proxy do end defp get_implementation_address_hash_string(proxy_address_hash, proxy_abi) do + get_implementation_address_hash_string_eip1167( + proxy_address_hash, + proxy_abi + ) + end + + @doc """ + Returns EIP-1167 implementation address or tries next proxy pattern + """ + @spec get_implementation_address_hash_string_eip1167(Hash.Address.t(), any()) :: String.t() | nil + def get_implementation_address_hash_string_eip1167(proxy_address_hash, proxy_abi) do + get_implementation_address_hash_string_by_module( + EIP1167, + :get_implementation_address_hash_string_eip1967, + [ + proxy_address_hash, + proxy_abi + ] + ) + end + + @doc """ + Returns EIP-1967 implementation address or tries next proxy pattern + """ + @spec get_implementation_address_hash_string_eip1967(Hash.Address.t(), any()) :: String.t() | nil + def get_implementation_address_hash_string_eip1967(proxy_address_hash, proxy_abi) do + get_implementation_address_hash_string_by_module( + EIP1967, + :get_implementation_address_hash_string_eip1822, + [ + proxy_address_hash, + proxy_abi + ] + ) + end + + @doc """ + Returns EIP-1822 implementation address or tries next proxy pattern + """ + @spec get_implementation_address_hash_string_eip1822(Hash.Address.t(), any()) :: String.t() | nil + def get_implementation_address_hash_string_eip1822(proxy_address_hash, proxy_abi) do + get_implementation_address_hash_string_by_module(EIP1822, [proxy_address_hash, proxy_abi]) + end + + defp get_implementation_address_hash_string_by_module( + module, + next_func \\ :fallback_proxy_detection, + [proxy_address_hash, _proxy_abi] = args + ) do + implementation_address_hash_string = module.get_implementation_address_hash_string(proxy_address_hash) + + if !is_nil(implementation_address_hash_string) && implementation_address_hash_string !== burn_address_hash_string() do + implementation_address_hash_string + else + apply(__MODULE__, next_func, args) + end + end + + @spec fallback_proxy_detection(Hash.Address.t(), any()) :: String.t() | nil + def fallback_proxy_detection(proxy_address_hash, proxy_abi) do implementation_method_abi = get_naive_implementation_abi(proxy_abi, "implementation") get_implementation_method_abi = get_naive_implementation_abi(proxy_abi, "getImplementation") + comptroller_implementation_method_abi = get_naive_implementation_abi(proxy_abi, "comptrollerImplementation") + master_copy_method_abi = get_master_copy_pattern(proxy_abi) get_address_method_abi = get_naive_implementation_abi(proxy_abi, "getAddress") @@ -173,13 +238,18 @@ defmodule Explorer.Chain.SmartContract.Proxy do master_copy_method_abi -> MasterCopy.get_implementation_address_hash_string(proxy_address_hash) + comptroller_implementation_method_abi -> + Basic.get_implementation_address_hash_string( + @comptroller_implementation_signature, + proxy_address_hash, + proxy_abi + ) + get_address_method_abi -> EIP930.get_implementation_address_hash_string(@get_address_signature, proxy_address_hash, proxy_abi) true -> - EIP1967.get_implementation_address_hash_string(proxy_address_hash) || - EIP1167.get_implementation_address_hash_string(proxy_address_hash) || - EIP1822.get_implementation_address_hash_string(proxy_address_hash) + nil end end @@ -197,6 +267,10 @@ defmodule Explorer.Chain.SmartContract.Proxy do end) end + @doc """ + Returns combined ABI from proxy and implementation smart-contracts + """ + @spec combine_proxy_implementation_abi(any(), any()) :: SmartContract.abi() def combine_proxy_implementation_abi(smart_contract, options \\ []) def combine_proxy_implementation_abi(%SmartContract{abi: abi} = smart_contract, options) when not is_nil(abi) do diff --git a/apps/explorer/lib/explorer/chain/smart_contract/proxy/eip_1967.ex b/apps/explorer/lib/explorer/chain/smart_contract/proxy/eip_1967.ex index aea3fa39be04..5cc06c75ed9e 100644 --- a/apps/explorer/lib/explorer/chain/smart_contract/proxy/eip_1967.ex +++ b/apps/explorer/lib/explorer/chain/smart_contract/proxy/eip_1967.ex @@ -27,18 +27,24 @@ defmodule Explorer.Chain.SmartContract.Proxy.EIP1967 do def get_implementation_address_hash_string(proxy_address_hash) do json_rpc_named_arguments = Application.get_env(:explorer, :json_rpc_named_arguments) - implementation_address_hash_string = + eip1967_implementation_address_hash_string = Proxy.get_implementation_from_storage( proxy_address_hash, @storage_slot_logic_contract_address, json_rpc_named_arguments ) || - fetch_beacon_proxy_implementation(proxy_address_hash, json_rpc_named_arguments) || + fetch_beacon_proxy_implementation(proxy_address_hash, json_rpc_named_arguments) + + implementation_address_hash_string = + if eip1967_implementation_address_hash_string do + eip1967_implementation_address_hash_string + else Proxy.get_implementation_from_storage( proxy_address_hash, @storage_slot_openzeppelin_contract_address, json_rpc_named_arguments ) + end Proxy.abi_decode_address_output(implementation_address_hash_string) end @@ -70,17 +76,16 @@ defmodule Explorer.Chain.SmartContract.Proxy.EIP1967 do nil {:ok, beacon_contract_address} -> - case beacon_contract_address - |> Proxy.abi_decode_address_output() + case @implementation_signature |> Basic.get_implementation_address_hash_string( - @implementation_signature, + beacon_contract_address, implementation_method_abi ) do <> -> implementation_address _ -> - beacon_contract_address + nil end _ -> diff --git a/apps/explorer/lib/explorer/chain/supply.ex b/apps/explorer/lib/explorer/chain/supply.ex index b442e4012597..0287a0dde80b 100644 --- a/apps/explorer/lib/explorer/chain/supply.ex +++ b/apps/explorer/lib/explorer/chain/supply.ex @@ -7,7 +7,7 @@ defmodule Explorer.Chain.Supply do """ @doc """ - The current total number of coins minted minus verifiably burned coins. + The current total number of coins minted minus verifiably burnt coins. """ @callback total :: non_neg_integer() | %Decimal{sign: 1} diff --git a/apps/explorer/lib/explorer/chain/token.ex b/apps/explorer/lib/explorer/chain/token.ex index fc12af368a0d..66c3fdd5e2de 100644 --- a/apps/explorer/lib/explorer/chain/token.ex +++ b/apps/explorer/lib/explorer/chain/token.ex @@ -23,10 +23,18 @@ defmodule Explorer.Chain.Token do import Ecto.{Changeset, Query} alias Ecto.Changeset - alias Explorer.{Chain, PagingOptions} - alias Explorer.Chain.{Address, Hash, Token} + alias Explorer.{Chain, SortingHelper} + alias Explorer.Chain.{Address, Hash, Search, Token} alias Explorer.SmartContract.Helper + @default_sorting [ + desc_nulls_last: :circulating_market_cap, + desc_nulls_last: :fiat_value, + desc_nulls_last: :holder_count, + asc: :name, + asc: :contract_address_hash + ] + @typedoc """ * `name` - Name of the token * `symbol` - Trading symbol of the token @@ -160,178 +168,45 @@ defmodule Explorer.Chain.Token do from(token in __MODULE__, where: token.contract_address_hash in ^contract_address_hashes) end - def base_token_query(type, sorting) do - query = from(t in Token, preload: [:contract_address]) - - query |> apply_filter(type) |> apply_sorting(sorting) - end - - defp apply_filter(query, empty_type) when empty_type in [nil, []], do: query - - defp apply_filter(query, token_types) when is_list(token_types) do - from(t in query, where: t.type in ^token_types) - end - - @default_sorting [ - desc_nulls_last: :circulating_market_cap, - desc_nulls_last: :holder_count, - asc: :name, - asc: :contract_address_hash - ] - - defp apply_sorting(query, sorting) when is_list(sorting) do - from(t in query, order_by: ^sorting_with_defaults(sorting)) - end - - defp sorting_with_defaults(sorting) when is_list(sorting) do - (sorting ++ @default_sorting) - |> Enum.uniq_by(fn {_, field} -> field end) - end - - def page_tokens(query, paging_options, sorting \\ []) - def page_tokens(query, %PagingOptions{key: nil}, _sorting), do: query - - def page_tokens( - query, - %PagingOptions{ - key: %{} = key - }, - sorting - ) do - dynamic_where = sorting |> sorting_with_defaults() |> do_page_tokens() - - from(token in query, - where: ^dynamic_where.(key) - ) - end - - defp do_page_tokens([{order, column} | rest]) do - fn key -> page_tokens_by_column(key, column, order, do_page_tokens(rest)) end - end - - defp do_page_tokens([]), do: nil - - defp page_tokens_by_column(%{fiat_value: nil} = key, :fiat_value, :desc_nulls_last, next_column) do - dynamic( - [t], - is_nil(t.fiat_value) and ^next_column.(key) - ) - end - - defp page_tokens_by_column(%{fiat_value: nil} = key, :fiat_value, :asc_nulls_first, next_column) do - next_column.(key) - end - - defp page_tokens_by_column(%{fiat_value: fiat_value} = key, :fiat_value, :desc_nulls_last, next_column) do - dynamic( - [t], - is_nil(t.fiat_value) or t.fiat_value < ^fiat_value or - (t.fiat_value == ^fiat_value and ^next_column.(key)) - ) - end - - defp page_tokens_by_column(%{fiat_value: fiat_value} = key, :fiat_value, :asc_nulls_first, next_column) do - dynamic( - [t], - not is_nil(t.fiat_value) and - (t.fiat_value > ^fiat_value or - (t.fiat_value == ^fiat_value and ^next_column.(key))) - ) - end - - defp page_tokens_by_column( - %{circulating_market_cap: nil} = key, - :circulating_market_cap, - :desc_nulls_last, - next_column - ) do - dynamic( - [t], - is_nil(t.circulating_market_cap) and ^next_column.(key) - ) - end - - defp page_tokens_by_column( - %{circulating_market_cap: nil} = key, - :circulating_market_cap, - :asc_nulls_first, - next_column - ) do - next_column.(key) - end - - defp page_tokens_by_column( - %{circulating_market_cap: circulating_market_cap} = key, - :circulating_market_cap, - :desc_nulls_last, - next_column - ) do - dynamic( - [t], - is_nil(t.circulating_market_cap) or t.circulating_market_cap < ^circulating_market_cap or - (t.circulating_market_cap == ^circulating_market_cap and ^next_column.(key)) - ) - end - - defp page_tokens_by_column( - %{circulating_market_cap: circulating_market_cap} = key, - :circulating_market_cap, - :asc_nulls_first, - next_column - ) do - dynamic( - [t], - not is_nil(t.circulating_market_cap) and - (t.circulating_market_cap > ^circulating_market_cap or - (t.circulating_market_cap == ^circulating_market_cap and ^next_column.(key))) - ) - end + @doc """ + Lists the top `t:__MODULE__.t/0`'s'. + """ + @spec list_top(String.t() | nil, [ + Chain.paging_options() + | {:sorting, SortingHelper.sorting_params()} + | {:token_type, [String.t()]} + ]) :: [Token.t()] + def list_top(filter, options \\ []) do + paging_options = Keyword.get(options, :paging_options, Chain.default_paging_options()) + token_type = Keyword.get(options, :token_type, nil) + sorting = Keyword.get(options, :sorting, []) - defp page_tokens_by_column(%{holder_count: nil} = key, :holder_count, :desc_nulls_last, next_column) do - dynamic( - [t], - is_nil(t.holder_count) and ^next_column.(key) - ) - end + query = from(t in Token, preload: [:contract_address]) - defp page_tokens_by_column(%{holder_count: nil} = key, :holder_count, :asc_nulls_first, next_column) do - next_column.(key) - end + sorted_paginated_query = + query + |> apply_filter(token_type) + |> SortingHelper.apply_sorting(sorting, @default_sorting) + |> SortingHelper.page_with_sorting(paging_options, sorting, @default_sorting) - defp page_tokens_by_column(%{holder_count: holder_count} = key, :holder_count, :desc_nulls_last, next_column) do - dynamic( - [t], - is_nil(t.holder_count) or t.holder_count < ^holder_count or - (t.holder_count == ^holder_count and ^next_column.(key)) - ) - end + filtered_query = + case filter && filter !== "" && Search.prepare_search_term(filter) do + {:some, filter_term} -> + sorted_paginated_query + |> where(fragment("to_tsvector('english', symbol || ' ' || name) @@ to_tsquery(?)", ^filter_term)) - defp page_tokens_by_column(%{holder_count: holder_count} = key, :holder_count, :asc_nulls_first, next_column) do - dynamic( - [t], - not is_nil(t.holder_count) and - (t.holder_count > ^holder_count or - (t.holder_count == ^holder_count and ^next_column.(key))) - ) - end + _ -> + sorted_paginated_query + end - defp page_tokens_by_column(%{name: nil} = key, :name, :asc, next_column) do - dynamic( - [t], - is_nil(t.name) and ^next_column.(key) - ) + filtered_query + |> Chain.select_repo(options).all() end - defp page_tokens_by_column(%{name: name} = key, :name, :asc, next_column) do - dynamic( - [t], - is_nil(t.name) or - (t.name > ^name or (t.name == ^name and ^next_column.(key))) - ) - end + defp apply_filter(query, empty_type) when empty_type in [nil, []], do: query - defp page_tokens_by_column(%{contract_address_hash: contract_address_hash}, :contract_address_hash, :asc, nil) do - dynamic([t], t.contract_address_hash > ^contract_address_hash) + defp apply_filter(query, token_types) when is_list(token_types) do + from(t in query, where: t.type in ^token_types) end def get_by_contract_address_hash(hash, options) do diff --git a/apps/explorer/lib/explorer/chain/token/instance.ex b/apps/explorer/lib/explorer/chain/token/instance.ex index 608aec33193e..43249f7c722b 100644 --- a/apps/explorer/lib/explorer/chain/token/instance.ex +++ b/apps/explorer/lib/explorer/chain/token/instance.ex @@ -26,7 +26,8 @@ defmodule Explorer.Chain.Token.Instance do owner_address_hash: Hash.Address.t(), owner_updated_at_block: Block.block_number(), owner_updated_at_log_index: non_neg_integer(), - current_token_balance: any() + current_token_balance: any(), + is_unique: bool() | nil } @primary_key false @@ -37,6 +38,7 @@ defmodule Explorer.Chain.Token.Instance do field(:owner_updated_at_block, :integer) field(:owner_updated_at_log_index, :integer) field(:current_token_balance, :any, virtual: true) + field(:is_unique, :boolean, virtual: true) belongs_to(:owner, Address, foreign_key: :owner_address_hash, references: :hash, type: Hash.Address) @@ -95,16 +97,13 @@ defmodule Explorer.Chain.Token.Instance do def page_token_instance(query, _), do: query def owner_query(%Instance{token_contract_address_hash: token_contract_address_hash, token_id: token_id}) do - from( - tt in TokenTransfer, - join: to_address in assoc(tt, :to_address), - where: - tt.token_contract_address_hash == ^token_contract_address_hash and - fragment("? @> ARRAY[?::decimal]", tt.token_ids, ^token_id), - order_by: [desc: tt.block_number], - limit: 1, - select: to_address + CurrentTokenBalance + |> where( + [ctb], + ctb.token_contract_address_hash == ^token_contract_address_hash and ctb.token_id == ^token_id and ctb.value > 0 ) + |> limit(1) + |> select([ctb], ctb.address_hash) end @spec token_instance_query(non_neg_integer(), Hash.Address.t()) :: Ecto.Query.t() @@ -224,7 +223,7 @@ defmodule Explorer.Chain.Token.Instance do %{"token_contract_address_hash" => token_contract_address_hash, "token_id" => token_id, "token_type" => "ERC-721"} end - @preloaded_nfts_limit 15 + @preloaded_nfts_limit 9 @spec nft_collections(binary() | Hash.Address.t(), keyword) :: list def nft_collections(address_hash, options \\ []) @@ -393,6 +392,7 @@ defmodule Explorer.Chain.Token.Instance do |> limit(^paging_options.page_size) |> page_token_instance(paging_options) |> Chain.select_repo(options).all() + |> Enum.map(&put_is_unique(&1, token, options)) end def token_instances_by_holder_address_hash(%Token{} = token, holder_address_hash, options) do @@ -412,5 +412,59 @@ defmodule Explorer.Chain.Token.Instance do |> page_token_instance(paging_options) |> select_merge([ctb: ctb], %{current_token_balance: ctb}) |> Chain.select_repo(options).all() + |> Enum.map(&put_is_unique(&1, token, options)) + end + + @doc """ + Finds token instances (pairs of contract_address_hash and token_id) which was met in token transfers but has no corresponding entry in token_instances table + """ + @spec not_inserted_token_instances_query(integer()) :: Ecto.Query.t() + def not_inserted_token_instances_query(limit) do + token_transfers_query = + TokenTransfer + |> where([token_transfer], not is_nil(token_transfer.token_ids) and token_transfer.token_ids != ^[]) + |> select([token_transfer], %{ + token_contract_address_hash: token_transfer.token_contract_address_hash, + token_id: fragment("unnest(?)", token_transfer.token_ids) + }) + + token_transfers_query + |> subquery() + |> join(:left, [token_transfer], token_instance in __MODULE__, + on: + token_instance.token_contract_address_hash == token_transfer.token_contract_address_hash and + token_instance.token_id == token_transfer.token_id + ) + |> where([token_transfer, token_instance], is_nil(token_instance.token_id)) + |> select([token_transfer, token_instance], %{ + contract_address_hash: token_transfer.token_contract_address_hash, + token_id: token_transfer.token_id + }) + |> limit(^limit) end + + def put_is_unique(instance, token, options) do + %__MODULE__{instance | is_unique: is_unique?(instance, token, options)} + end + + defp is_unique?( + %Instance{current_token_balance: %CurrentTokenBalance{value: %Decimal{} = value}} = instance, + token, + options + ) do + if Decimal.compare(value, 1) == :gt do + false + else + is_unique?(%Instance{instance | current_token_balance: nil}, token, options) + end + end + + defp is_unique?(%Instance{current_token_balance: %CurrentTokenBalance{value: value}}, _token, _options) + when value > 1, + do: false + + defp is_unique?(instance, token, options), + do: + not (token.type == "ERC-1155") or + Chain.token_id_1155_is_unique?(token.contract_address_hash, instance.token_id, options) end diff --git a/apps/explorer/lib/explorer/chain/token_transfer.ex b/apps/explorer/lib/explorer/chain/token_transfer.ex index 1c5a15123f51..38dad6123521 100644 --- a/apps/explorer/lib/explorer/chain/token_transfer.ex +++ b/apps/explorer/lib/explorer/chain/token_transfer.ex @@ -28,7 +28,7 @@ defmodule Explorer.Chain.TokenTransfer do import Ecto.Query, only: [from: 2, limit: 2, where: 3, join: 5, order_by: 3, preload: 3] alias Explorer.Chain - alias Explorer.Chain.{Address, Block, Hash, TokenTransfer, Transaction} + alias Explorer.Chain.{Address, Block, DenormalizationHelper, Hash, Log, TokenTransfer, Transaction} alias Explorer.Chain.Token.Instance alias Explorer.{PagingOptions, Repo} @@ -44,7 +44,6 @@ defmodule Explorer.Chain.TokenTransfer do * `:to_address_hash` - Address hash foreign key * `:token_contract_address` - The `t:Explorer.Chain.Address.t/0` of the token's contract. * `:token_contract_address_hash` - Address hash foreign key - * `:token_id` - ID of the token (applicable to ERC-721 tokens) * `:transaction` - The `t:Explorer.Chain.Transaction.t/0` ledger * `:transaction_hash` - Transaction foreign key * `:log_index` - Index of the corresponding `t:Explorer.Chain.Log.t/0` in the block. @@ -61,7 +60,6 @@ defmodule Explorer.Chain.TokenTransfer do to_address_hash: Hash.Address.t(), token_contract_address: %Ecto.Association.NotLoaded{} | Address.t(), token_contract_address_hash: Hash.Address.t(), - token_id: non_neg_integer() | nil, transaction: %Ecto.Association.NotLoaded{} | Transaction.t(), transaction_hash: Hash.Full.t(), log_index: non_neg_integer(), @@ -86,7 +84,6 @@ defmodule Explorer.Chain.TokenTransfer do field(:amount, :decimal) field(:block_number, :integer) field(:log_index, :integer, primary_key: true) - field(:token_id, :decimal) field(:amounts, {:array, :decimal}) field(:token_ids, {:array, :decimal}) field(:index_in_batch, :integer, virtual: true) @@ -129,7 +126,7 @@ defmodule Explorer.Chain.TokenTransfer do end @required_attrs ~w(block_number log_index from_address_hash to_address_hash token_contract_address_hash transaction_hash block_hash)a - @optional_attrs ~w(amount token_id amounts token_ids)a + @optional_attrs ~w(amount amounts token_ids)a @doc false def changeset(%TokenTransfer{} = struct, params \\ %{}) do @@ -161,12 +158,13 @@ defmodule Explorer.Chain.TokenTransfer do @spec fetch_token_transfers_from_token_hash(Hash.t(), [paging_options | api?]) :: [] def fetch_token_transfers_from_token_hash(token_address_hash, options) do paging_options = Keyword.get(options, :paging_options, @default_paging_options) + preloads = DenormalizationHelper.extend_transaction_preload([:transaction, :token, :from_address, :to_address]) query = from( tt in TokenTransfer, where: tt.token_contract_address_hash == ^token_address_hash and not is_nil(tt.block_number), - preload: [{:transaction, :block}, :token, :from_address, :to_address], + preload: ^preloads, order_by: [desc: tt.block_number, desc: tt.log_index] ) @@ -179,6 +177,7 @@ defmodule Explorer.Chain.TokenTransfer do @spec fetch_token_transfers_from_token_hash_and_token_id(Hash.t(), non_neg_integer(), [paging_options | api?]) :: [] def fetch_token_transfers_from_token_hash_and_token_id(token_address_hash, token_id, options) do paging_options = Keyword.get(options, :paging_options, @default_paging_options) + preloads = DenormalizationHelper.extend_transaction_preload([:transaction, :token, :from_address, :to_address]) query = from( @@ -186,7 +185,7 @@ defmodule Explorer.Chain.TokenTransfer do where: tt.token_contract_address_hash == ^token_address_hash, where: fragment("? @> ARRAY[?::decimal]", tt.token_ids, ^Decimal.new(token_id)), where: not is_nil(tt.block_number), - preload: [{:transaction, :block}, :token, :from_address, :to_address], + preload: ^preloads, order_by: [desc: tt.block_number, desc: tt.log_index] ) @@ -368,4 +367,31 @@ defmodule Explorer.Chain.TokenTransfer do where: block.consensus == true ) end + + @doc """ + Returns a list of block numbers token transfer `t:Log.t/0`s that don't have an + associated `t:TokenTransfer.t/0` record. + """ + @spec uncataloged_token_transfer_block_numbers :: {:ok, [non_neg_integer()]} + def uncataloged_token_transfer_block_numbers do + query = + from(l in Log, + as: :log, + where: + l.first_topic == ^@constant or + l.first_topic == ^@erc1155_single_transfer_signature or + l.first_topic == ^@erc1155_batch_transfer_signature, + where: + not exists( + from(tf in TokenTransfer, + where: tf.transaction_hash == parent_as(:log).transaction_hash, + where: tf.log_index == parent_as(:log).index + ) + ), + select: l.block_number, + distinct: l.block_number + ) + + Repo.stream_reduce(query, [], &[&1 | &2]) + end end diff --git a/apps/explorer/lib/explorer/chain/transaction.ex b/apps/explorer/lib/explorer/chain/transaction.ex index 0b9820a941e2..3bc2e101805b 100644 --- a/apps/explorer/lib/explorer/chain/transaction.ex +++ b/apps/explorer/lib/explorer/chain/transaction.ex @@ -5,20 +5,19 @@ defmodule Explorer.Chain.Transaction do require Logger - import Ecto.Query, only: [from: 2, preload: 3, subquery: 1, where: 3] - alias ABI.FunctionSelector alias Ecto.Association.NotLoaded alias Ecto.Changeset - alias Explorer.Chain + alias Explorer.{Chain, Repo} alias Explorer.Chain.{ Address, Block, ContractMethod, Data, + DenormalizationHelper, Gas, Hash, InternalTransaction, @@ -31,12 +30,14 @@ defmodule Explorer.Chain.Transaction do Wei } + alias Explorer.Chain.Block.Reward alias Explorer.Chain.SmartContract.Proxy alias Explorer.Chain.Transaction.{Fork, Status} alias Explorer.Chain.Zkevm.BatchTransaction + alias Explorer.{PagingOptions, SortingHelper} alias Explorer.SmartContract.SigProviderInterface - @optional_attrs ~w(max_priority_fee_per_gas max_fee_per_gas block_hash block_number created_contract_address_hash cumulative_gas_used earliest_processing_start + @optional_attrs ~w(max_priority_fee_per_gas max_fee_per_gas block_hash block_number block_consensus block_timestamp created_contract_address_hash cumulative_gas_used earliest_processing_start error gas_price gas_used index created_contract_code_indexed_at status to_address_hash revert_reason type has_error_in_internal_txs near_receipt_hash near_transaction_hash)a @suave_optional_attrs ~w(execution_node_hash wrapped_type wrapped_nonce wrapped_to_address_hash wrapped_gas wrapped_gas_price wrapped_max_priority_fee_per_gas wrapped_max_fee_per_gas wrapped_value wrapped_input wrapped_v wrapped_r wrapped_s wrapped_hash)a @@ -91,6 +92,8 @@ defmodule Explorer.Chain.Transaction do `uncles` in one of the `forks`. * `block_number` - Denormalized `block` `number`. `nil` when transaction is pending or has only been collated into one of the `uncles` in one of the `forks`. + * `block_consensus` - consensus of the block where transaction collated. + * `block_timestamp` - timestamp of the block where transaction collated. * `created_contract_address` - belongs_to association to `address` corresponding to `created_contract_address_hash`. * `created_contract_address_hash` - Denormalized `internal_transaction` `created_contract_address_hash` populated only when `to_address_hash` is nil. @@ -169,6 +172,8 @@ defmodule Explorer.Chain.Transaction do block: %Ecto.Association.NotLoaded{} | Block.t() | nil, block_hash: Hash.t() | nil, block_number: Block.block_number() | nil, + block_consensus: boolean(), + block_timestamp: DateTime.t() | nil, created_contract_address: %Ecto.Association.NotLoaded{} | Address.t() | nil, created_contract_address_hash: Hash.Address.t() | nil, created_contract_code_indexed_at: DateTime.t() | nil, @@ -234,6 +239,7 @@ defmodule Explorer.Chain.Transaction do @derive {Poison.Encoder, only: [ :block_number, + :block_timestamp, :cumulative_gas_used, :error, :gas, @@ -254,6 +260,7 @@ defmodule Explorer.Chain.Transaction do @derive {Jason.Encoder, only: [ :block_number, + :block_timestamp, :cumulative_gas_used, :error, :gas, @@ -274,6 +281,8 @@ defmodule Explorer.Chain.Transaction do @primary_key {:hash, Hash.Full, autogenerate: false} schema "transactions" do field(:block_number, :integer) + field(:block_consensus, :boolean) + field(:block_timestamp, :utc_datetime_usec) field(:cumulative_gas_used, :decimal) field(:earliest_processing_start, :utc_datetime_usec) field(:error, :string) @@ -548,6 +557,11 @@ defmodule Explorer.Chain.Transaction do |> unique_constraint(:hash) end + @spec block_timestamp(t()) :: DateTime.t() + def block_timestamp(%{block_number: nil, inserted_at: time}), do: time + def block_timestamp(%{block_timestamp: time}) when not is_nil(time), do: time + def block_timestamp(%{block: %{timestamp: time}}), do: time + def preload_token_transfers(query, address_hash) do token_transfers_query = from( @@ -598,6 +612,18 @@ defmodule Explorer.Chain.Transaction do end # Because there is no contract association, we know the contract was not verified + @spec decoded_input_data( + NotLoaded.t() | Transaction.t(), + boolean(), + [Chain.api?()], + full_abi_acc, + methods_acc + ) :: + {error_type | success_type, full_abi_acc, methods_acc} + when full_abi_acc: map(), + methods_acc: map(), + error_type: {:error, any()} | {:error, :contract_not_verified | :contract_verified, list()}, + success_type: {:ok | binary(), any()} | {:ok, binary(), binary(), list()} def decoded_input_data(tx, skip_sig_provider? \\ false, options, full_abi_acc \\ %{}, methods_acc \\ %{}) def decoded_input_data(%__MODULE__{to_address: nil}, _, _, full_abi_acc, methods_acc), @@ -778,12 +804,7 @@ defmodule Explorer.Chain.Transaction do if Map.has_key?(methods_acc, method_id) do {methods_acc[method_id], methods_acc} else - candidates_query = - from( - contract_method in ContractMethod, - where: contract_method.identifier == ^method_id, - limit: 1 - ) + candidates_query = ContractMethod.find_contract_method_query(method_id, 1) result = candidates_query @@ -1016,11 +1037,12 @@ defmodule Explorer.Chain.Transaction do """ def transactions_with_token_transfers(address_hash, token_hash) do query = transactions_with_token_transfers_query(address_hash, token_hash) + preloads = DenormalizationHelper.extend_block_preload([:from_address, :to_address, :created_contract_address]) from( t in subquery(query), order_by: [desc: t.block_number, desc: t.index], - preload: [:from_address, :to_address, :created_contract_address, :block] + preload: ^preloads ) end @@ -1037,11 +1059,12 @@ defmodule Explorer.Chain.Transaction do def transactions_with_token_transfers_direction(direction, address_hash) do query = transactions_with_token_transfers_query_direction(direction, address_hash) + preloads = DenormalizationHelper.extend_block_preload([:from_address, :to_address, :created_contract_address]) from( t in subquery(query), order_by: [desc: t.block_number, desc: t.index], - preload: [:from_address, :to_address, :created_contract_address, :block] + preload: ^preloads ) end @@ -1185,7 +1208,7 @@ defmodule Explorer.Chain.Transaction do Enum.map_reduce(transactions, %{}, fn transaction, tokens_acc -> case Log.fetch_log_by_tx_hash_and_first_topic(transaction.hash, @transaction_fee_event_signature, @api_true) do fee_log when not is_nil(fee_log) -> - {:ok, _selector, mapping} = Log.find_and_decode(@transaction_fee_event_abi, fee_log, transaction) + {:ok, _selector, mapping} = Log.find_and_decode(@transaction_fee_event_abi, fee_log, transaction.hash) [{"token", "address", false, token_address_hash}, _, _, _, _, _] = mapping @@ -1217,4 +1240,444 @@ defmodule Explorer.Chain.Transaction do end def bytes_to_address_hash(bytes), do: %Hash{byte_count: 20, bytes: bytes} + + @doc """ + Fetches the transactions related to the address with the given hash, including + transactions that only have the address in the `token_transfers` related table + and rewards for block validation. + + This query is divided into multiple subqueries intentionally in order to + improve the listing performance. + + The `token_transfers` table tends to grow exponentially, and the query results + with a `transactions` `join` statement takes too long. + + To solve this the `transaction_hashes` are fetched in a separate query, and + paginated through the `block_number` already present in the `token_transfers` + table. + + ## Options + + * `:necessity_by_association` - use to load `t:association/0` as `:required` or `:optional`. If an association is + `:required`, and the `t:Explorer.Chain.Transaction.t/0` has no associated record for that association, then the + `t:Explorer.Chain.Transaction.t/0` will not be included in the page `entries`. + * `:paging_options` - a `t:Explorer.PagingOptions.t/0` used to specify the `:page_size` and + `:key` (a tuple of the lowest/oldest `{block_number, index}`) and. Results will be the transactions older than + the `block_number` and `index` that are passed. + + """ + @spec address_to_transactions_with_rewards(Hash.Address.t(), [ + Chain.paging_options() | Chain.necessity_by_association_option() + ]) :: [__MODULE__.t()] + def address_to_transactions_with_rewards(address_hash, options \\ []) when is_list(options) do + paging_options = Keyword.get(options, :paging_options, Chain.default_paging_options()) + + case Application.get_env(:block_scout_web, BlockScoutWeb.Chain)[:has_emission_funds] && + Keyword.get(options, :direction) != :from && + Reward.address_has_rewards?(address_hash) && + Reward.get_validator_payout_key_by_mining_from_db(address_hash, options) do + %{payout_key: block_miner_payout_address} + when not is_nil(block_miner_payout_address) and address_hash == block_miner_payout_address -> + transactions_with_rewards_results(address_hash, options, paging_options) + + _ -> + address_to_transactions_without_rewards(address_hash, options) + end + end + + defp transactions_with_rewards_results(address_hash, options, paging_options) do + blocks_range = address_to_transactions_tasks_range_of_blocks(address_hash, options) + + rewards_task = + Task.async(fn -> Reward.fetch_emission_rewards_tuples(address_hash, paging_options, blocks_range, options) end) + + [rewards_task | address_to_transactions_tasks(address_hash, options, true)] + |> wait_for_address_transactions() + |> Enum.sort_by(fn item -> + case item do + {%Reward{} = emission_reward, _} -> + {-emission_reward.block.number, 1} + + item -> + process_item(item) + end + end) + |> Enum.dedup_by(fn item -> + case item do + {%Reward{} = emission_reward, _} -> + {emission_reward.block_hash, emission_reward.address_hash, emission_reward.address_type} + + transaction -> + transaction.hash + end + end) + |> Enum.take(paging_options.page_size) + end + + @doc false + def address_to_transactions_tasks_range_of_blocks(address_hash, options) do + extremums_list = + address_hash + |> transactions_block_numbers_at_address(options) + |> Enum.map(fn query -> + extremum_query = + from( + q in subquery(query), + select: %{min_block_number: min(q.block_number), max_block_number: max(q.block_number)} + ) + + extremum_query + |> Repo.one!() + end) + + extremums_list + |> Enum.reduce(%{min_block_number: nil, max_block_number: 0}, fn %{ + min_block_number: min_number, + max_block_number: max_number + }, + extremums_result -> + current_min_number = Map.get(extremums_result, :min_block_number) + current_max_number = Map.get(extremums_result, :max_block_number) + + extremums_result + |> process_extremums_result_against_min_number(current_min_number, min_number) + |> process_extremums_result_against_max_number(current_max_number, max_number) + end) + end + + defp transactions_block_numbers_at_address(address_hash, options) do + direction = Keyword.get(options, :direction) + + options + |> address_to_transactions_tasks_query(true) + |> not_pending_transactions() + |> select([t], t.block_number) + |> matching_address_queries_list(direction, address_hash) + end + + defp process_extremums_result_against_min_number(extremums_result, current_min_number, min_number) + when is_number(current_min_number) and + not (is_number(min_number) and min_number > 0 and min_number < current_min_number) do + extremums_result + end + + defp process_extremums_result_against_min_number(extremums_result, _current_min_number, min_number) do + extremums_result + |> Map.put(:min_block_number, min_number) + end + + defp process_extremums_result_against_max_number(extremums_result, current_max_number, max_number) + when is_number(max_number) and max_number > 0 and max_number > current_max_number do + extremums_result + |> Map.put(:max_block_number, max_number) + end + + defp process_extremums_result_against_max_number(extremums_result, _current_max_number, _max_number) do + extremums_result + end + + defp process_item(item) do + block_number = if item.block_number, do: -item.block_number, else: 0 + index = if item.index, do: -item.index, else: 0 + {block_number, index} + end + + @spec address_to_transactions_without_rewards( + Hash.Address.t(), + [ + Chain.paging_options() + | Chain.necessity_by_association_option() + | {:sorting, SortingHelper.sorting_params()} + ], + boolean() + ) :: [__MODULE__.t()] + def address_to_transactions_without_rewards(address_hash, options, old_ui? \\ true) do + paging_options = Keyword.get(options, :paging_options, Chain.default_paging_options()) + + address_hash + |> address_to_transactions_tasks(options, old_ui?) + |> wait_for_address_transactions() + |> Enum.sort(compare_custom_sorting(Keyword.get(options, :sorting, []))) + |> Enum.dedup_by(& &1.hash) + |> Enum.take(paging_options.page_size) + end + + defp address_to_transactions_tasks(address_hash, options, old_ui?) do + direction = Keyword.get(options, :direction) + necessity_by_association = Keyword.get(options, :necessity_by_association, %{}) + old_ui? = old_ui? || is_tuple(Keyword.get(options, :paging_options, Chain.default_paging_options()).key) + + options + |> address_to_transactions_tasks_query(false, old_ui?) + |> not_dropped_or_replaced_transactions() + |> Chain.join_associations(necessity_by_association) + |> put_has_token_transfers_to_tx(old_ui?) + |> matching_address_queries_list(direction, address_hash) + |> Enum.map(fn query -> Task.async(fn -> Chain.select_repo(options).all(query) end) end) + end + + @doc """ + Returns the address to transactions tasks query based on provided options. + Boolean `only_mined?` argument specifies if only mined transactions should be returned, + boolean `old_ui?` argument specifies if the query is for the old UI, i.e. is query dynamically sorted or no. + """ + @spec address_to_transactions_tasks_query(keyword, boolean, boolean) :: Ecto.Query.t() + def address_to_transactions_tasks_query(options, only_mined? \\ false, old_ui? \\ true) + + def address_to_transactions_tasks_query(options, only_mined?, true) do + from_block = Chain.from_block(options) + to_block = Chain.to_block(options) + + options + |> Keyword.get(:paging_options, Chain.default_paging_options()) + |> fetch_transactions(from_block, to_block, !only_mined?) + end + + def address_to_transactions_tasks_query(options, _only_mined?, false) do + from_block = Chain.from_block(options) + to_block = Chain.to_block(options) + paging_options = Keyword.get(options, :paging_options, Chain.default_paging_options()) + sorting_options = Keyword.get(options, :sorting, []) + + fetch_transactions_with_custom_sorting(paging_options, from_block, to_block, sorting_options) + end + + @doc """ + Waits for the address transactions tasks to complete and returns the transactions flattened + in case of success or raises an error otherwise. + """ + @spec wait_for_address_transactions([Task.t()]) :: [__MODULE__.t()] + def wait_for_address_transactions(tasks) do + tasks + |> Task.yield_many(:timer.seconds(20)) + |> Enum.flat_map(fn {_task, res} -> + case res do + {:ok, result} -> + result + + {:exit, reason} -> + raise "Query fetching address transactions terminated: #{inspect(reason)}" + + nil -> + raise "Query fetching address transactions timed out." + end + end) + end + + defp compare_custom_sorting([{order, :value}]) do + fn a, b -> + case Decimal.compare(Wei.to(a.value, :wei), Wei.to(b.value, :wei)) do + :eq -> compare_default_sorting(a, b) + :gt -> order == :desc + :lt -> order == :asc + end + end + end + + defp compare_custom_sorting([{:dynamic, :fee, order, _dynamic_fee}]) do + fn a, b -> + nil_case = + case order do + :desc_nulls_last -> Decimal.new("-inf") + :asc_nulls_first -> Decimal.new("inf") + end + + a_fee = a |> Chain.fee(:wei) |> elem(1) || nil_case + b_fee = b |> Chain.fee(:wei) |> elem(1) || nil_case + + case Decimal.compare(a_fee, b_fee) do + :eq -> compare_default_sorting(a, b) + :gt -> order == :desc_nulls_last + :lt -> order == :asc_nulls_first + end + end + end + + defp compare_custom_sorting([]), do: &compare_default_sorting/2 + + defp compare_default_sorting(a, b) do + case { + compare(a.block_number, b.block_number), + compare(a.index, b.index), + DateTime.compare(a.inserted_at, b.inserted_at), + compare(Hash.to_integer(a.hash), Hash.to_integer(b.hash)) + } do + {:lt, _, _, _} -> false + {:eq, :lt, _, _} -> false + {:eq, :eq, :lt, _} -> false + {:eq, :eq, :eq, :gt} -> false + _ -> true + end + end + + defp compare(a, b) do + cond do + a < b -> :lt + a > b -> :gt + true -> :eq + end + end + + @doc """ + Creates a query to fetch transactions taking into account paging_options (possibly nil), + from_block (may be nil), to_block (may be nil) and boolean `with_pending?` that indicates if pending transactions should be included + into the query. + """ + @spec fetch_transactions(PagingOptions.t() | nil, non_neg_integer | nil, non_neg_integer | nil, boolean()) :: + Ecto.Query.t() + def fetch_transactions(paging_options \\ nil, from_block \\ nil, to_block \\ nil, with_pending? \\ false) do + __MODULE__ + |> order_for_transactions(with_pending?) + |> Chain.where_block_number_in_period(from_block, to_block) + |> handle_paging_options(paging_options) + end + + @default_sorting [ + desc: :block_number, + desc: :index, + desc: :inserted_at, + asc: :hash + ] + + @doc """ + Creates a query to fetch transactions taking into account paging_options (possibly nil), + from_block (may be nil), to_block (may be nil) and sorting_params. + """ + @spec fetch_transactions_with_custom_sorting( + PagingOptions.t() | nil, + non_neg_integer | nil, + non_neg_integer | nil, + SortingHelper.sorting_params() + ) :: Ecto.Query.t() + def fetch_transactions_with_custom_sorting(paging_options, from_block, to_block, sorting) do + query = from(transaction in __MODULE__) + + query + |> Chain.where_block_number_in_period(from_block, to_block) + |> SortingHelper.apply_sorting(sorting, @default_sorting) + |> SortingHelper.page_with_sorting(paging_options, sorting, @default_sorting) + end + + defp order_for_transactions(query, true) do + query + |> order_by([transaction], + desc: transaction.block_number, + desc: transaction.index, + desc: transaction.inserted_at, + asc: transaction.hash + ) + end + + defp order_for_transactions(query, _) do + query + |> order_by([transaction], desc: transaction.block_number, desc: transaction.index) + end + + @doc """ + Updates the provided query with necessary `where`s and `limit`s to take into account paging_options (may be nil). + """ + @spec handle_paging_options(Ecto.Query.t() | atom, nil | Explorer.PagingOptions.t()) :: Ecto.Query.t() + def handle_paging_options(query, nil), do: query + + def handle_paging_options(query, %PagingOptions{key: nil, page_size: nil}), do: query + + def handle_paging_options(query, paging_options) do + query + |> page_transaction(paging_options) + |> limit(^paging_options.page_size) + end + + @doc """ + Updates the provided query with necessary `where`s to take into account paging_options. + """ + @spec page_transaction(Ecto.Query.t() | atom, Explorer.PagingOptions.t()) :: Ecto.Query.t() + def page_transaction(query, %PagingOptions{key: nil}), do: query + + def page_transaction(query, %PagingOptions{is_pending_tx: true} = options), + do: page_pending_transaction(query, options) + + def page_transaction(query, %PagingOptions{key: {block_number, index}, is_index_in_asc_order: true}) do + where( + query, + [transaction], + transaction.block_number < ^block_number or + (transaction.block_number == ^block_number and transaction.index > ^index) + ) + end + + def page_transaction(query, %PagingOptions{key: {block_number, index}}) do + where( + query, + [transaction], + transaction.block_number < ^block_number or + (transaction.block_number == ^block_number and transaction.index < ^index) + ) + end + + def page_transaction(query, %PagingOptions{key: {index}}) do + where(query, [transaction], transaction.index < ^index) + end + + @doc """ + Updates the provided query with necessary `where`s to take into account paging_options. + """ + @spec page_pending_transaction(Ecto.Query.t() | atom, Explorer.PagingOptions.t()) :: Ecto.Query.t() + def page_pending_transaction(query, %PagingOptions{key: nil}), do: query + + def page_pending_transaction(query, %PagingOptions{key: {inserted_at, hash}}) do + where( + query, + [transaction], + (is_nil(transaction.block_number) and + (transaction.inserted_at < ^inserted_at or + (transaction.inserted_at == ^inserted_at and transaction.hash > ^hash))) or + not is_nil(transaction.block_number) + ) + end + + @doc """ + Adds a `has_token_transfers` field to the query via `select_merge` if second argument is `false` and returns + the query untouched otherwise. + """ + @spec put_has_token_transfers_to_tx(Ecto.Query.t() | atom, boolean) :: Ecto.Query.t() + def put_has_token_transfers_to_tx(query, true), do: query + + def put_has_token_transfers_to_tx(query, false) do + from(tx in query, + select_merge: %{ + has_token_transfers: + fragment( + "(SELECT transaction_hash FROM token_transfers WHERE transaction_hash = ? LIMIT 1) IS NOT NULL", + tx.hash + ) + } + ) + end + + @doc """ + Return the dynamic that calculates the fee for transactions. + """ + @spec dynamic_fee :: struct() + def dynamic_fee do + dynamic([tx], tx.gas_price * fragment("COALESCE(?, ?)", tx.gas_used, tx.gas)) + end + + @doc """ + Returns next page params based on the provided transaction. + """ + @spec address_transactions_next_page_params(Explorer.Chain.Transaction.t()) :: %{ + required(String.t()) => Decimal.t() | Wei.t() | non_neg_integer | DateTime.t() | Hash.t() + } + def address_transactions_next_page_params( + %__MODULE__{block_number: block_number, index: index, inserted_at: inserted_at, hash: hash, value: value} = tx + ) do + %{ + "fee" => tx |> Chain.fee(:wei) |> elem(1), + "value" => value, + "block_number" => block_number, + "index" => index, + "inserted_at" => inserted_at, + "hash" => hash + } + end end diff --git a/apps/explorer/lib/explorer/chain/transaction/history/historian.ex b/apps/explorer/lib/explorer/chain/transaction/history/historian.ex index d828cb63e794..2c8e4479cf6d 100644 --- a/apps/explorer/lib/explorer/chain/transaction/history/historian.ex +++ b/apps/explorer/lib/explorer/chain/transaction/history/historian.ex @@ -6,7 +6,7 @@ defmodule Explorer.Chain.Transaction.History.Historian do use Explorer.History.Historian alias Explorer.{Chain, Repo} - alias Explorer.Chain.{Block, Transaction} + alias Explorer.Chain.{Block, DenormalizationHelper, Transaction} alias Explorer.Chain.Events.Publisher alias Explorer.Chain.Transaction.History.TransactionStats alias Explorer.History.Process, as: HistoryProcess @@ -89,25 +89,38 @@ defmodule Explorer.Chain.Transaction.History.Historian do Logger.info("tx/per day chart: min/max block numbers [#{min_block}, #{max_block}]") all_transactions_query = - from( - transaction in Transaction, - where: transaction.block_number >= ^min_block and transaction.block_number <= ^max_block - ) + if DenormalizationHelper.denormalization_finished?() do + from( + transaction in Transaction, + where: transaction.block_number >= ^min_block and transaction.block_number <= ^max_block, + where: transaction.block_consensus == true, + select: transaction + ) + else + from( + transaction in Transaction, + where: transaction.block_number >= ^min_block and transaction.block_number <= ^max_block + ) + end all_blocks_query = from( block in Block, where: block.consensus == true, where: block.number >= ^min_block and block.number <= ^max_block, - select: block.hash + select: block.number ) query = - from(transaction in subquery(all_transactions_query), - join: block in subquery(all_blocks_query), - on: transaction.block_hash == block.hash, - select: transaction - ) + if DenormalizationHelper.denormalization_finished?() do + all_transactions_query + else + from(transaction in subquery(all_transactions_query), + join: block in subquery(all_blocks_query), + on: transaction.block_number == block.number, + select: transaction + ) + end num_transactions = Repo.aggregate(query, :count, :hash, timeout: :infinity) Logger.info("tx/per day chart: num of transactions #{num_transactions}") @@ -115,11 +128,18 @@ defmodule Explorer.Chain.Transaction.History.Historian do Logger.info("tx/per day chart: total gas used #{gas_used}") total_fee_query = - from(transaction in subquery(all_transactions_query), - join: block in subquery(all_blocks_query), - on: transaction.block_hash == block.hash, - select: fragment("SUM(? * ?)", transaction.gas_price, transaction.gas_used) - ) + if DenormalizationHelper.denormalization_finished?() do + from(transaction in subquery(all_transactions_query), + select: fragment("SUM(? * ?)", transaction.gas_price, transaction.gas_used) + ) + else + from(transaction in subquery(all_transactions_query), + join: block in Block, + on: transaction.block_hash == block.hash, + where: block.consensus == true, + select: fragment("SUM(? * ?)", transaction.gas_price, transaction.gas_used) + ) + end total_fee = Repo.one(total_fee_query, timeout: :infinity) Logger.info("tx/per day chart: total fee #{total_fee}") diff --git a/apps/explorer/lib/explorer/chain/transaction/state_change.ex b/apps/explorer/lib/explorer/chain/transaction/state_change.ex index 3a399aa019f1..c0b70f23c493 100644 --- a/apps/explorer/lib/explorer/chain/transaction/state_change.ex +++ b/apps/explorer/lib/explorer/chain/transaction/state_change.ex @@ -120,7 +120,7 @@ defmodule Explorer.Chain.Transaction.StateChange do end defp do_update_balance(old_val, type, transfer, _) do - token_ids = if transfer.token.type == "ERC-1155", do: transfer.token_ids || [transfer.token_id], else: [nil] + token_ids = if transfer.token.type == "ERC-1155", do: transfer.token_ids, else: [nil] transfer_amounts = transfer.amounts || [transfer.amount || 1] sub_or_add = diff --git a/apps/explorer/lib/explorer/chain/wei.ex b/apps/explorer/lib/explorer/chain/wei.ex index 533174ba4524..c542c9081a13 100644 --- a/apps/explorer/lib/explorer/chain/wei.ex +++ b/apps/explorer/lib/explorer/chain/wei.ex @@ -138,13 +138,24 @@ defmodule Explorer.Chain.Wei do iex> Explorer.Chain.Wei.sum(first, second) %Explorer.Chain.Wei{value: Decimal.new(1_123)} """ - @spec sum(Wei.t(), Wei.t()) :: Wei.t() + @spec sum(Wei.t() | nil, Wei.t() | nil) :: Wei.t() | nil + def sum(%Wei{value: wei_1}, %Wei{value: nil}) do + wei_1 + |> from(:wei) + end + + def sum(%Wei{value: nil}, %Wei{value: wei_2}) do + wei_2 + |> from(:wei) + end + def sum(%Wei{value: wei_1}, %Wei{value: wei_2}) do wei_1 |> Decimal.add(wei_2) |> from(:wei) end + @spec sub(Wei.t(), Wei.t()) :: Wei.t() | nil @doc """ Subtracts two Wei values. @@ -155,6 +166,8 @@ defmodule Explorer.Chain.Wei do iex> Explorer.Chain.Wei.sub(first, second) %Explorer.Chain.Wei{value: Decimal.new(123)} """ + def sub(_, nil), do: nil + def sub(%Wei{value: wei_1}, %Wei{value: wei_2}) do wei_1 |> Decimal.sub(wei_2) @@ -206,17 +219,23 @@ defmodule Explorer.Chain.Wei do """ - @spec from(ether(), :ether) :: t() + @spec from(ether() | nil, :ether) :: t() | nil + def from(nil, :ether), do: nil + def from(%Decimal{} = ether, :ether) do %__MODULE__{value: Decimal.mult(ether, @wei_per_ether)} end - @spec from(gwei(), :gwei) :: t() + @spec from(gwei(), :gwei) :: t() | nil + def from(nil, :gwei), do: nil + def from(%Decimal{} = gwei, :gwei) do %__MODULE__{value: Decimal.mult(gwei, @wei_per_gwei)} end @spec from(wei(), :wei) :: t() + def from(nil, :wei), do: nil + def from(%Decimal{} = wei, :wei) do %__MODULE__{value: wei} end @@ -247,17 +266,22 @@ defmodule Explorer.Chain.Wei do """ - @spec to(t(), :ether) :: ether() + @spec to(t(), :ether) :: ether() | nil + def to(nil, :ether), do: nil + def to(%__MODULE__{value: wei}, :ether) do Decimal.div(wei, @wei_per_ether) end - @spec to(t(), :gwei) :: gwei() + @spec to(t(), :gwei) :: gwei() | nil + def to(nil, :gwei), do: nil + def to(%__MODULE__{value: wei}, :gwei) do Decimal.div(wei, @wei_per_gwei) end - @spec to(t(), :wei) :: wei() + @spec to(t(), :wei) :: wei() | nil + def to(nil, :wei), do: nil def to(%__MODULE__{value: wei}, :wei), do: wei end diff --git a/apps/explorer/lib/explorer/counters/block_burned_fee_counter.ex b/apps/explorer/lib/explorer/counters/block_burnt_fee_counter.ex similarity index 89% rename from apps/explorer/lib/explorer/counters/block_burned_fee_counter.ex rename to apps/explorer/lib/explorer/counters/block_burnt_fee_counter.ex index 85e9359f4294..9c87e313ffe7 100644 --- a/apps/explorer/lib/explorer/counters/block_burned_fee_counter.ex +++ b/apps/explorer/lib/explorer/counters/block_burnt_fee_counter.ex @@ -1,15 +1,15 @@ -defmodule Explorer.Counters.BlockBurnedFeeCounter do +defmodule Explorer.Counters.BlockBurntFeeCounter do @moduledoc """ - Caches Block Burned Fee counter. + Caches Block Burnt Fee counter. """ use GenServer alias Explorer.Chain alias Explorer.Counters.Helper - @cache_name :block_burned_fee_counter + @cache_name :block_burnt_fee_counter - config = Application.compile_env(:explorer, Explorer.Counters.BlockBurnedFeeCounter) + config = Application.compile_env(:explorer, __MODULE__) @enable_consolidation Keyword.get(config, :enable_consolidation) @spec start_link(term()) :: GenServer.on_start() diff --git a/apps/explorer/lib/explorer/counters/block_priority_fee_counter.ex b/apps/explorer/lib/explorer/counters/block_priority_fee_counter.ex index f51deb4e2e7a..152679fe830d 100644 --- a/apps/explorer/lib/explorer/counters/block_priority_fee_counter.ex +++ b/apps/explorer/lib/explorer/counters/block_priority_fee_counter.ex @@ -1,6 +1,6 @@ defmodule Explorer.Counters.BlockPriorityFeeCounter do @moduledoc """ - Caches Block Burned Fee counter. + Caches Block Priority Fee counter. """ use GenServer diff --git a/apps/explorer/lib/explorer/eth_rpc.ex b/apps/explorer/lib/explorer/eth_rpc.ex index 889fdabbba6e..d7260cf3987c 100644 --- a/apps/explorer/lib/explorer/eth_rpc.ex +++ b/apps/explorer/lib/explorer/eth_rpc.ex @@ -54,13 +54,13 @@ defmodule Explorer.EthRPC do action: :eth_get_logs, notes: """ Will never return more than 1000 log entries.\n - For this reason, you can use pagination options to request the next page. Pagination options params: {"logIndex": "3D", "blockNumber": "6423AC", "transactionIndex": 53} which include parameters from the last log received from the previous request. These three parameters are required for pagination. + For this reason, you can use pagination options to request the next page. Pagination options params: {"logIndex": "3D", "blockNumber": "6423AC"} which include parameters from the last log received from the previous request. These three parameters are required for pagination. """, example: """ {"id": 0, "jsonrpc": "2.0", "method": "eth_getLogs", "params": [ {"address": "0xc78Be425090Dbd437532594D12267C5934Cc6c6f", - "paging_options": {"logIndex": "3D", "blockNumber": "6423AC", "transactionIndex": 53}, + "paging_options": {"logIndex": "3D", "blockNumber": "6423AC"}, "fromBlock": "earliest", "toBlock": "latest", "topics": ["0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef"]}]} @@ -180,8 +180,7 @@ defmodule Explorer.EthRPC do "topics" => topics, "transactionHash" => to_string(log.transaction_hash), "transactionIndex" => log.transaction_index, - "transactionLogIndex" => log.index, - "type" => "mined" + "transactionLogIndex" => log.index } end diff --git a/apps/explorer/lib/explorer/etherscan.ex b/apps/explorer/lib/explorer/etherscan.ex index 4663c3c9b9db..023f74e9dadb 100644 --- a/apps/explorer/lib/explorer/etherscan.ex +++ b/apps/explorer/lib/explorer/etherscan.ex @@ -9,7 +9,7 @@ defmodule Explorer.Etherscan do alias Explorer.Etherscan.Logs alias Explorer.{Chain, Repo} alias Explorer.Chain.Address.{CurrentTokenBalance, TokenBalance} - alias Explorer.Chain.{Address, Block, Hash, InternalTransaction, TokenTransfer, Transaction} + alias Explorer.Chain.{Address, Block, DenormalizationHelper, Hash, InternalTransaction, TokenTransfer, Transaction} alias Explorer.Chain.Transaction.History.TransactionStats @default_options %{ @@ -101,18 +101,32 @@ defmodule Explorer.Etherscan do @spec list_internal_transactions(Hash.Full.t()) :: [map()] def list_internal_transactions(%Hash{byte_count: unquote(Hash.Full.byte_count())} = transaction_hash) do query = - from( - it in InternalTransaction, - inner_join: t in assoc(it, :transaction), - inner_join: b in assoc(t, :block), - where: it.transaction_hash == ^transaction_hash, - limit: 10_000, - select: - merge(map(it, ^@internal_transaction_fields), %{ - block_timestamp: b.timestamp, - block_number: b.number - }) - ) + if DenormalizationHelper.denormalization_finished?() do + from( + it in InternalTransaction, + inner_join: transaction in assoc(it, :transaction), + where: it.transaction_hash == ^transaction_hash, + limit: 10_000, + select: + merge(map(it, ^@internal_transaction_fields), %{ + block_timestamp: transaction.block_timestamp, + block_number: transaction.block_number + }) + ) + else + from( + it in InternalTransaction, + inner_join: t in assoc(it, :transaction), + inner_join: b in assoc(t, :block), + where: it.transaction_hash == ^transaction_hash, + limit: 10_000, + select: + merge(map(it, ^@internal_transaction_fields), %{ + block_timestamp: b.timestamp, + block_number: b.number + }) + ) + end query |> Chain.where_transaction_has_multiple_internal_transactions() @@ -158,8 +172,8 @@ defmodule Explorer.Etherscan do query = from( it in InternalTransaction, - inner_join: b in subquery(consensus_blocks), - on: it.block_number == b.number, + inner_join: block in subquery(consensus_blocks), + on: it.block_number == block.number, order_by: [ {^options.order_by_direction, it.block_number}, {:desc, it.transaction_index}, @@ -169,8 +183,8 @@ defmodule Explorer.Etherscan do offset: ^offset(options), select: merge(map(it, ^@internal_transaction_fields), %{ - block_timestamp: b.timestamp, - block_number: b.number + block_timestamp: block.timestamp, + block_number: block.number }) ) @@ -212,19 +226,34 @@ defmodule Explorer.Etherscan do |> Repo.replica().all() else query = - from( - it in InternalTransaction, - inner_join: t in assoc(it, :transaction), - inner_join: b in assoc(t, :block), - order_by: [{^options.order_by_direction, t.block_number}], - limit: ^options.page_size, - offset: ^offset(options), - select: - merge(map(it, ^@internal_transaction_fields), %{ - block_timestamp: b.timestamp, - block_number: b.number - }) - ) + if DenormalizationHelper.denormalization_finished?() do + from( + it in InternalTransaction, + inner_join: transaction in assoc(it, :transaction), + order_by: [{^options.order_by_direction, transaction.block_number}], + limit: ^options.page_size, + offset: ^offset(options), + select: + merge(map(it, ^@internal_transaction_fields), %{ + block_timestamp: transaction.block_timestamp, + block_number: transaction.block_number + }) + ) + else + from( + it in InternalTransaction, + inner_join: t in assoc(it, :transaction), + inner_join: b in assoc(t, :block), + order_by: [{^options.order_by_direction, t.block_number}], + limit: ^options.page_size, + offset: ^offset(options), + select: + merge(map(it, ^@internal_transaction_fields), %{ + block_timestamp: b.timestamp, + block_number: b.number + }) + ) + end query |> Chain.where_transaction_has_multiple_internal_transactions() @@ -279,14 +308,14 @@ defmodule Explorer.Etherscan do query = from( - b in Block, - where: b.miner_hash == ^address_hash, - order_by: [desc: b.number], + block in Block, + where: block.miner_hash == ^address_hash, + order_by: [desc: block.number], limit: ^merged_options.page_size, offset: ^offset(merged_options), select: %{ - number: b.number, - timestamp: b.timestamp + number: block.number, + timestamp: block.timestamp } ) @@ -343,6 +372,8 @@ defmodule Explorer.Etherscan do @transaction_fields ~w( block_hash block_number + block_consensus + block_timestamp created_contract_address_hash cumulative_gas_used from_address_hash @@ -393,23 +424,36 @@ defmodule Explorer.Etherscan do defp list_transactions(address_hash, max_block_number, options) do query = - from( - t in Transaction, - inner_join: b in assoc(t, :block), - order_by: [{^options.order_by_direction, t.block_number}], - limit: ^options.page_size, - offset: ^offset(options), - select: - merge(map(t, ^@transaction_fields), %{ - block_timestamp: b.timestamp, - confirmations: fragment("? - ?", ^max_block_number, t.block_number) - }) - ) + if DenormalizationHelper.denormalization_finished?() do + from( + t in Transaction, + order_by: [{^options.order_by_direction, t.block_number}], + limit: ^options.page_size, + offset: ^offset(options), + select: + merge(map(t, ^@transaction_fields), %{ + confirmations: fragment("? - ?", ^max_block_number, t.block_number) + }) + ) + else + from( + t in Transaction, + inner_join: b in assoc(t, :block), + order_by: [{^options.order_by_direction, t.block_number}], + limit: ^options.page_size, + offset: ^offset(options), + select: + merge(map(t, ^@transaction_fields), %{ + block_timestamp: b.timestamp, + confirmations: fragment("? - ?", ^max_block_number, t.block_number) + }) + ) + end query |> where_address_match(address_hash, options) - |> where_start_block_match(options) - |> where_end_block_match(options) + |> where_start_transaction_block_match(options) + |> where_end_transaction_block_match(options) |> where_start_timestamp_match(options) |> where_end_timestamp_match(options) |> Repo.replica().all() @@ -467,42 +511,76 @@ defmodule Explorer.Etherscan do |> where_contract_address_match(contract_address_hash) wrapped_query = - from( - tt in subquery(tt_specific_token_query), - inner_join: t in Transaction, - on: tt.transaction_hash == t.hash and tt.block_number == t.block_number and tt.block_hash == t.block_hash, - inner_join: b in assoc(t, :block), - order_by: [{^options.order_by_direction, tt.block_number}, {^options.order_by_direction, tt.token_log_index}], - select: %{ - token_contract_address_hash: tt.token_contract_address_hash, - transaction_hash: tt.transaction_hash, - from_address_hash: tt.from_address_hash, - to_address_hash: tt.to_address_hash, - amount: tt.amount, - amounts: tt.amounts, - transaction_nonce: t.nonce, - transaction_index: t.index, - transaction_gas: t.gas, - transaction_gas_price: t.gas_price, - transaction_gas_used: t.gas_used, - transaction_cumulative_gas_used: t.cumulative_gas_used, - transaction_input: t.input, - block_hash: b.hash, - block_number: b.number, - block_timestamp: b.timestamp, - confirmations: fragment("? - ?", ^block_height, t.block_number), - token_ids: tt.token_ids, - token_name: tt.token_name, - token_symbol: tt.token_symbol, - token_decimals: tt.token_decimals, - token_type: tt.token_type, - token_log_index: tt.token_log_index - } - ) + if DenormalizationHelper.denormalization_finished?() do + from( + tt in subquery(tt_specific_token_query), + inner_join: t in Transaction, + on: tt.transaction_hash == t.hash and tt.block_number == t.block_number and tt.block_hash == t.block_hash, + order_by: [{^options.order_by_direction, tt.block_number}, {^options.order_by_direction, tt.token_log_index}], + select: %{ + token_contract_address_hash: tt.token_contract_address_hash, + transaction_hash: tt.transaction_hash, + from_address_hash: tt.from_address_hash, + to_address_hash: tt.to_address_hash, + amount: tt.amount, + amounts: tt.amounts, + transaction_nonce: t.nonce, + transaction_index: t.index, + transaction_gas: t.gas, + transaction_gas_price: t.gas_price, + transaction_gas_used: t.gas_used, + transaction_cumulative_gas_used: t.cumulative_gas_used, + transaction_input: t.input, + block_hash: t.block_hash, + block_number: t.block_number, + block_timestamp: t.block_timestamp, + confirmations: fragment("? - ?", ^block_height, t.block_number), + token_ids: tt.token_ids, + token_name: tt.token_name, + token_symbol: tt.token_symbol, + token_decimals: tt.token_decimals, + token_type: tt.token_type, + token_log_index: tt.token_log_index + } + ) + else + from( + tt in subquery(tt_specific_token_query), + inner_join: t in Transaction, + on: tt.transaction_hash == t.hash and tt.block_number == t.block_number and tt.block_hash == t.block_hash, + inner_join: b in assoc(t, :block), + order_by: [{^options.order_by_direction, tt.block_number}, {^options.order_by_direction, tt.token_log_index}], + select: %{ + token_contract_address_hash: tt.token_contract_address_hash, + transaction_hash: tt.transaction_hash, + from_address_hash: tt.from_address_hash, + to_address_hash: tt.to_address_hash, + amount: tt.amount, + amounts: tt.amounts, + transaction_nonce: t.nonce, + transaction_index: t.index, + transaction_gas: t.gas, + transaction_gas_price: t.gas_price, + transaction_gas_used: t.gas_used, + transaction_cumulative_gas_used: t.cumulative_gas_used, + transaction_input: t.input, + block_hash: b.hash, + block_number: b.number, + block_timestamp: b.timestamp, + confirmations: fragment("? - ?", ^block_height, t.block_number), + token_ids: tt.token_ids, + token_name: tt.token_name, + token_symbol: tt.token_symbol, + token_decimals: tt.token_decimals, + token_type: tt.token_type, + token_log_index: tt.token_log_index + } + ) + end wrapped_query - |> where_start_block_match(options) - |> where_end_block_match(options) + |> where_start_transaction_block_match(options) + |> where_end_transaction_block_match(options) |> Repo.replica().all() end @@ -518,16 +596,44 @@ defmodule Explorer.Etherscan do where(query, [..., block], block.number <= ^end_block) end + defp where_start_transaction_block_match(query, %{start_block: nil}), do: query + + defp where_start_transaction_block_match(query, %{start_block: start_block} = params) do + if DenormalizationHelper.denormalization_finished?() do + where(query, [transaction], transaction.block_number >= ^start_block) + else + where_start_block_match(query, params) + end + end + + defp where_end_transaction_block_match(query, %{end_block: nil}), do: query + + defp where_end_transaction_block_match(query, %{end_block: end_block} = params) do + if DenormalizationHelper.denormalization_finished?() do + where(query, [transaction], transaction.block_number <= ^end_block) + else + where_end_block_match(query, params) + end + end + defp where_start_timestamp_match(query, %{start_timestamp: nil}), do: query defp where_start_timestamp_match(query, %{start_timestamp: start_timestamp}) do - where(query, [..., block], ^start_timestamp <= block.timestamp) + if DenormalizationHelper.denormalization_finished?() do + where(query, [transaction], ^start_timestamp <= transaction.block_timestamp) + else + where(query, [..., block], ^start_timestamp <= block.timestamp) + end end defp where_end_timestamp_match(query, %{end_timestamp: nil}), do: query defp where_end_timestamp_match(query, %{end_timestamp: end_timestamp}) do - where(query, [..., block], block.timestamp <= ^end_timestamp) + if DenormalizationHelper.denormalization_finished?() do + where(query, [transaction], transaction.block_timestamp <= ^end_timestamp) + else + where(query, [..., block], block.timestamp <= ^end_timestamp) + end end defp where_contract_address_match(query, nil), do: query diff --git a/apps/explorer/lib/explorer/etherscan/logs.ex b/apps/explorer/lib/explorer/etherscan/logs.ex index c7ae91e34732..a0c432ce4432 100644 --- a/apps/explorer/lib/explorer/etherscan/logs.ex +++ b/apps/explorer/lib/explorer/etherscan/logs.ex @@ -5,10 +5,10 @@ defmodule Explorer.Etherscan.Logs do """ - import Ecto.Query, only: [from: 2, where: 3, subquery: 1, order_by: 3, union: 2] + import Ecto.Query, only: [from: 2, limit: 2, where: 3, subquery: 1, order_by: 3, union: 2] alias Explorer.{Chain, Repo} - alias Explorer.Chain.{Block, InternalTransaction, Log, Transaction} + alias Explorer.Chain.{Block, DenormalizationHelper, InternalTransaction, Log, Transaction} @base_filter %{ from_block: nil, @@ -34,11 +34,10 @@ defmodule Explorer.Etherscan.Logs do :fourth_topic, :index, :address_hash, - :transaction_hash, - :type + :transaction_hash ] - @default_paging_options %{block_number: nil, transaction_index: nil, log_index: nil} + @default_paging_options %{block_number: nil, log_index: nil} @doc """ Gets a list of logs that meet the criteria in a given filter map. @@ -76,77 +75,126 @@ defmodule Explorer.Etherscan.Logs do paging_options = if is_nil(paging_options), do: @default_paging_options, else: paging_options prepared_filter = Map.merge(@base_filter, filter) - logs_query = where_topic_match(Log, prepared_filter) - - query_to_address_hash_wrapped = - logs_query - |> internal_transaction_query(:to_address_hash, prepared_filter, address_hash) - |> Chain.wrapped_union_subquery() - - query_from_address_hash_wrapped = - logs_query - |> internal_transaction_query(:from_address_hash, prepared_filter, address_hash) - |> Chain.wrapped_union_subquery() - - query_created_contract_address_hash_wrapped = - logs_query - |> internal_transaction_query(:created_contract_address_hash, prepared_filter, address_hash) - |> Chain.wrapped_union_subquery() - - internal_transaction_log_query = - query_to_address_hash_wrapped - |> union(^query_from_address_hash_wrapped) - |> union(^query_created_contract_address_hash_wrapped) + if DenormalizationHelper.denormalization_finished?() do + logs_query = + Log + |> where_topic_match(prepared_filter) + |> where([log], log.address_hash == ^address_hash) + |> where([log], log.block_number >= ^prepared_filter.from_block) + |> where([log], log.block_number <= ^prepared_filter.to_block) + |> limit(1000) + |> order_by([log], asc: log.block_number, asc: log.index) + |> page_logs(paging_options) + + all_transaction_logs_query = + from(log in subquery(logs_query), + join: transaction in Transaction, + on: log.transaction_hash == transaction.hash, + select: map(log, ^@log_fields), + select_merge: %{ + gas_price: transaction.gas_price, + gas_used: transaction.gas_used, + transaction_index: transaction.index, + block_hash: transaction.block_hash, + block_number: transaction.block_number, + block_timestamp: transaction.block_timestamp, + block_consensus: transaction.block_consensus + } + ) - all_transaction_logs_query = - from(transaction in Transaction, - join: log in ^logs_query, - on: log.transaction_hash == transaction.hash, - where: transaction.block_number >= ^prepared_filter.from_block, - where: transaction.block_number <= ^prepared_filter.to_block, - where: - transaction.to_address_hash == ^address_hash or - transaction.from_address_hash == ^address_hash or - transaction.created_contract_address_hash == ^address_hash, - select: map(log, ^@log_fields), - select_merge: %{ - gas_price: transaction.gas_price, - gas_used: transaction.gas_used, - transaction_index: transaction.index, - block_number: transaction.block_number - }, - union: ^internal_transaction_log_query - ) + query_with_blocks = + from(log_transaction_data in subquery(all_transaction_logs_query), + where: log_transaction_data.address_hash == ^address_hash, + order_by: log_transaction_data.block_number, + select_merge: %{ + block_consensus: log_transaction_data.block_consensus + } + ) - query_with_blocks = - from(log_transaction_data in subquery(all_transaction_logs_query), - join: block in Block, - on: block.number == log_transaction_data.block_number, - where: log_transaction_data.address_hash == ^address_hash, - order_by: block.number, - limit: 1000, - select_merge: %{ - transaction_index: log_transaction_data.transaction_index, - block_hash: block.hash, - block_number: block.number, - block_timestamp: block.timestamp, - block_consensus: block.consensus - } - ) + query_with_consensus = + if Map.get(filter, :allow_non_consensus) do + query_with_blocks + else + from([transaction] in query_with_blocks, + where: transaction.block_consensus == true + ) + end + + query_with_consensus + |> Repo.replica().all() + else + logs_query = where_topic_match(Log, prepared_filter) + + query_to_address_hash_wrapped = + logs_query + |> internal_transaction_query(:to_address_hash, prepared_filter, address_hash) + |> Chain.wrapped_union_subquery() + + query_from_address_hash_wrapped = + logs_query + |> internal_transaction_query(:from_address_hash, prepared_filter, address_hash) + |> Chain.wrapped_union_subquery() + + query_created_contract_address_hash_wrapped = + logs_query + |> internal_transaction_query(:created_contract_address_hash, prepared_filter, address_hash) + |> Chain.wrapped_union_subquery() + + internal_transaction_log_query = + query_to_address_hash_wrapped + |> union(^query_from_address_hash_wrapped) + |> union(^query_created_contract_address_hash_wrapped) + + all_transaction_logs_query = + from(transaction in Transaction, + join: log in ^logs_query, + on: log.transaction_hash == transaction.hash, + where: transaction.block_number >= ^prepared_filter.from_block, + where: transaction.block_number <= ^prepared_filter.to_block, + where: + transaction.to_address_hash == ^address_hash or + transaction.from_address_hash == ^address_hash or + transaction.created_contract_address_hash == ^address_hash, + select: map(log, ^@log_fields), + select_merge: %{ + gas_price: transaction.gas_price, + gas_used: transaction.gas_used, + transaction_index: transaction.index, + block_number: transaction.block_number + }, + union: ^internal_transaction_log_query + ) - query_with_consensus = - if Map.get(filter, :allow_non_consensus) do - query_with_blocks - else - from([_, block] in query_with_blocks, - where: block.consensus == true + query_with_blocks = + from(log_transaction_data in subquery(all_transaction_logs_query), + join: block in Block, + on: block.number == log_transaction_data.block_number, + where: log_transaction_data.address_hash == ^address_hash, + order_by: block.number, + limit: 1000, + select_merge: %{ + transaction_index: log_transaction_data.transaction_index, + block_hash: block.hash, + block_number: block.number, + block_timestamp: block.timestamp, + block_consensus: block.consensus + } ) - end - query_with_consensus - |> order_by([log], asc: log.index) - |> page_logs(paging_options) - |> Repo.replica().all() + query_with_consensus = + if Map.get(filter, :allow_non_consensus) do + query_with_blocks + else + from([_, block] in query_with_blocks, + where: block.consensus == true + ) + end + + query_with_consensus + |> order_by([log], asc: log.index) + |> page_logs(paging_options) + |> Repo.replica().all() + end end # Since address_hash was not present, we know that a @@ -156,49 +204,90 @@ defmodule Explorer.Etherscan.Logs do def list_logs(filter, paging_options) do paging_options = if is_nil(paging_options), do: @default_paging_options, else: paging_options prepared_filter = Map.merge(@base_filter, filter) - logs_query = where_topic_match(Log, prepared_filter) - block_transaction_query = - from(transaction in Transaction, - join: block in assoc(transaction, :block), - where: block.number >= ^prepared_filter.from_block, - where: block.number <= ^prepared_filter.to_block, - select: %{ - transaction_hash: transaction.hash, - gas_price: transaction.gas_price, - gas_used: transaction.gas_used, - transaction_index: transaction.index, - block_hash: block.hash, - block_number: block.number, - block_timestamp: block.timestamp, - block_consensus: block.consensus - } - ) + if DenormalizationHelper.denormalization_finished?() do + block_transaction_query = + from(transaction in Transaction, + where: transaction.block_number >= ^prepared_filter.from_block, + where: transaction.block_number <= ^prepared_filter.to_block, + select: %{ + transaction_hash: transaction.hash, + gas_price: transaction.gas_price, + gas_used: transaction.gas_used, + transaction_index: transaction.index, + block_hash: transaction.block_hash, + block_number: transaction.block_number, + block_timestamp: transaction.block_timestamp, + block_consensus: transaction.block_consensus + } + ) - query_with_consensus = - if Map.get(filter, :allow_non_consensus) do - block_transaction_query - else - from([_, block] in block_transaction_query, - where: block.consensus == true + query_with_consensus = + if Map.get(filter, :allow_non_consensus) do + block_transaction_query + else + from([transaction] in block_transaction_query, + where: transaction.block_consensus == true + ) + end + + query_with_block_transaction_data = + from(log in logs_query, + join: block_transaction_data in subquery(query_with_consensus), + on: block_transaction_data.transaction_hash == log.transaction_hash, + order_by: block_transaction_data.block_number, + limit: 1000, + select: block_transaction_data, + select_merge: map(log, ^@log_fields) ) - end - - query_with_block_transaction_data = - from(log in logs_query, - join: block_transaction_data in subquery(query_with_consensus), - on: block_transaction_data.transaction_hash == log.transaction_hash, - order_by: block_transaction_data.block_number, - limit: 1000, - select: block_transaction_data, - select_merge: map(log, ^@log_fields) - ) - query_with_block_transaction_data - |> order_by([log], asc: log.index) - |> page_logs(paging_options) - |> Repo.replica().all() + query_with_block_transaction_data + |> order_by([log], asc: log.index) + |> page_logs(paging_options) + |> Repo.replica().all() + else + block_transaction_query = + from(transaction in Transaction, + join: block in assoc(transaction, :block), + where: block.number >= ^prepared_filter.from_block, + where: block.number <= ^prepared_filter.to_block, + select: %{ + transaction_hash: transaction.hash, + gas_price: transaction.gas_price, + gas_used: transaction.gas_used, + transaction_index: transaction.index, + block_hash: block.hash, + block_number: block.number, + block_timestamp: block.timestamp, + block_consensus: block.consensus + } + ) + + query_with_consensus = + if Map.get(filter, :allow_non_consensus) do + block_transaction_query + else + from([_, block] in block_transaction_query, + where: block.consensus == true + ) + end + + query_with_block_transaction_data = + from(log in logs_query, + join: block_transaction_data in subquery(query_with_consensus), + on: block_transaction_data.transaction_hash == log.transaction_hash, + order_by: block_transaction_data.block_number, + limit: 1000, + select: block_transaction_data, + select_merge: map(log, ^@log_fields) + ) + + query_with_block_transaction_data + |> order_by([log], asc: log.index) + |> page_logs(paging_options) + |> Repo.replica().all() + end end @topics [ diff --git a/apps/explorer/lib/explorer/exchange_rates/exchange_rates.ex b/apps/explorer/lib/explorer/exchange_rates/exchange_rates.ex index 93155d93b851..7ffe82536881 100644 --- a/apps/explorer/lib/explorer/exchange_rates/exchange_rates.ex +++ b/apps/explorer/lib/explorer/exchange_rates/exchange_rates.ex @@ -84,9 +84,11 @@ defmodule Explorer.ExchangeRates do @doc """ Lists exchange rates for the tracked tickers. """ - @spec list :: [Token.t()] + @spec list :: [Token.t()] | nil def list do - list_from_store(store()) + if enabled?() do + list_from_store(store()) + end end @doc """ diff --git a/apps/explorer/lib/explorer/exchange_rates/source.ex b/apps/explorer/lib/explorer/exchange_rates/source.ex index befa6560d068..0c7a0929651f 100644 --- a/apps/explorer/lib/explorer/exchange_rates/source.ex +++ b/apps/explorer/lib/explorer/exchange_rates/source.ex @@ -6,6 +6,7 @@ defmodule Explorer.ExchangeRates.Source do alias Explorer.Chain.Hash alias Explorer.ExchangeRates.Source.CoinGecko alias Explorer.ExchangeRates.Token + alias Explorer.Helper alias HTTPoison.{Error, Response} @doc """ @@ -91,12 +92,6 @@ defmodule Explorer.ExchangeRates.Source do [{"Content-Type", "application/json"}] end - def decode_json(data) do - Jason.decode!(data) - rescue - _ -> data - end - def to_decimal(nil), do: nil def to_decimal(%Decimal{} = value), do: value @@ -125,7 +120,7 @@ defmodule Explorer.ExchangeRates.Source do parse_http_success_response(body) {:ok, %Response{body: body, status_code: status_code}} when status_code in 400..526 -> - parse_http_error_response(body) + parse_http_error_response(body, status_code) {:ok, %Response{status_code: status_code}} when status_code in 300..308 -> {:error, "Source redirected"} @@ -135,37 +130,22 @@ defmodule Explorer.ExchangeRates.Source do {:error, %Error{reason: reason}} -> {:error, reason} - - {:error, :nxdomain} -> - {:error, "Source is not responsive"} - - {:error, _} -> - {:error, "Source unknown response"} end end defp parse_http_success_response(body) do - body_json = decode_json(body) + body_json = Helper.decode_json(body) - cond do - is_map(body_json) -> - {:ok, body_json} - - is_list(body_json) -> - {:ok, body_json} - - true -> - {:ok, body} - end + {:ok, body_json} end - defp parse_http_error_response(body) do - body_json = decode_json(body) + defp parse_http_error_response(body, status_code) do + body_json = Helper.decode_json(body) if is_map(body_json) do - {:error, body_json["error"]} + {:error, "#{status_code}: #{body_json["error"]}"} else - {:error, body} + {:error, "#{status_code}: #{body}"} end end end diff --git a/apps/explorer/lib/explorer/helper.ex b/apps/explorer/lib/explorer/helper.ex index 5004b1f26702..20d65314b23e 100644 --- a/apps/explorer/lib/explorer/helper.ex +++ b/apps/explorer/lib/explorer/helper.ex @@ -58,4 +58,38 @@ defmodule Explorer.Helper do Enum.map(list, fn el -> Map.put(el, preload_field, associated_elements[el[foreign_key_field]]) end) end + + @doc """ + Decode json + """ + @spec decode_json(any()) :: map() | list() | nil + def decode_json(nil), do: nil + + def decode_json(data) do + if String.valid?(data) do + safe_decode_json(data) + else + data + |> :unicode.characters_to_binary(:latin1) + |> safe_decode_json() + end + end + + defp safe_decode_json(data) do + case Jason.decode(data) do + {:ok, decoded} -> decoded + _ -> %{error: data} + end + end + + @doc """ + Tries to decode binary to json, return either decoded object, or initial binary + """ + @spec maybe_decode(binary) :: any + def maybe_decode(data) do + case safe_decode_json(data) do + %{error: _} -> data + decoded -> decoded + end + end end diff --git a/apps/explorer/lib/explorer/market/market.ex b/apps/explorer/lib/explorer/market/market.ex index 5e99a147c387..6de43a1b75b6 100644 --- a/apps/explorer/lib/explorer/market/market.ex +++ b/apps/explorer/lib/explorer/market/market.ex @@ -52,9 +52,9 @@ defmodule Explorer.Market do @doc """ Get most recent exchange rate for the native coin from ETS or from DB. """ - @spec get_coin_exchange_rate() :: Token.t() | nil + @spec get_coin_exchange_rate() :: Token.t() def get_coin_exchange_rate do - get_exchange_rate(Explorer.coin()) || get_native_coin_exchange_rate_from_db() || Token.null() + get_native_coin_exchange_rate_from_cache() || get_native_coin_exchange_rate_from_db() || Token.null() end @doc false @@ -138,8 +138,11 @@ defmodule Explorer.Market do ) end - @spec get_exchange_rate(String.t()) :: Token.t() | nil - defp get_exchange_rate(symbol) do - ExchangeRates.lookup(symbol) + @spec get_native_coin_exchange_rate_from_cache :: Token.t() | nil + defp get_native_coin_exchange_rate_from_cache do + case ExchangeRates.list() do + [native_coin] -> native_coin + _ -> nil + end end end diff --git a/apps/explorer/lib/explorer/market/market_history.ex b/apps/explorer/lib/explorer/market/market_history.ex index 0fabaf0e305c..8c1411879ee3 100644 --- a/apps/explorer/lib/explorer/market/market_history.ex +++ b/apps/explorer/lib/explorer/market/market_history.ex @@ -23,10 +23,10 @@ defmodule Explorer.Market.MarketHistory do * `:market_cap` - TVL in USD. """ @type t :: %__MODULE__{ - closing_price: Decimal.t(), + closing_price: Decimal.t() | nil, date: Date.t(), - opening_price: Decimal.t(), - market_cap: Decimal.t(), - tvl: Decimal.t() + opening_price: Decimal.t() | nil, + market_cap: Decimal.t() | nil, + tvl: Decimal.t() | nil } end diff --git a/apps/explorer/lib/explorer/microservice_interfaces/bens.ex b/apps/explorer/lib/explorer/microservice_interfaces/bens.ex new file mode 100644 index 000000000000..59eb1c82403f --- /dev/null +++ b/apps/explorer/lib/explorer/microservice_interfaces/bens.ex @@ -0,0 +1,424 @@ +defmodule Explorer.MicroserviceInterfaces.BENS do + @moduledoc """ + Interface to interact with Blockscout ENS microservice + """ + + alias Ecto.Association.NotLoaded + alias Explorer.Chain + + alias Explorer.Chain.{ + Address, + Address.CurrentTokenBalance, + Block, + InternalTransaction, + Log, + TokenTransfer, + Transaction, + Withdrawal + } + + alias Explorer.Utility.Microservice + alias HTTPoison.Response + require Logger + + @post_timeout :timer.seconds(5) + @request_error_msg "Error while sending request to BENS microservice" + + @typep supported_types :: + Address.t() + | Block.t() + | CurrentTokenBalance.t() + | InternalTransaction.t() + | Log.t() + | TokenTransfer.t() + | Transaction.t() + | Withdrawal.t() + + @doc """ + Batch request for ENS names via POST {{baseUrl}}/api/v1/:chainId/addresses:batch-resolve-names + """ + @spec ens_names_batch_request([binary()]) :: {:error, :disabled | binary() | Jason.DecodeError.t()} | {:ok, any} + def ens_names_batch_request(addresses) do + with :ok <- Microservice.check_enabled(__MODULE__) do + body = %{ + addresses: Enum.map(addresses, &to_string/1) + } + + http_post_request(batch_resolve_name_url(), body) + end + end + + @doc """ + Request for ENS name via GET {{baseUrl}}/api/v1/:chainId/addresses:lookup + """ + @spec address_lookup(binary()) :: {:error, :disabled | binary() | Jason.DecodeError.t()} | {:ok, any} + def address_lookup(address) do + with :ok <- Microservice.check_enabled(__MODULE__) do + query_params = %{ + "address" => to_string(address), + "resolved_to" => true, + "owned_by" => false, + "only_active" => true, + "order" => "ASC" + } + + http_get_request(address_lookup_url(), query_params) + end + end + + @doc """ + Lookup for ENS domain name via GET {{baseUrl}}/api/v1/:chainId/domains:lookup + """ + @spec ens_domain_lookup(binary()) :: {:error, :disabled | binary() | Jason.DecodeError.t()} | {:ok, any} + def ens_domain_lookup(domain) do + with :ok <- Microservice.check_enabled(__MODULE__) do + query_params = %{ + "name" => domain, + "only_active" => true, + "sort" => "registration_date", + "order" => "DESC" + } + + http_get_request(domain_lookup_url(), query_params) + end + end + + defp http_post_request(url, body) do + headers = [{"Content-Type", "application/json"}] + + case HTTPoison.post(url, Jason.encode!(body), headers, recv_timeout: @post_timeout) do + {:ok, %Response{body: body, status_code: 200}} -> + Jason.decode(body) + + {_, error} -> + old_truncate = Application.get_env(:logger, :truncate) + Logger.configure(truncate: :infinity) + + Logger.error(fn -> + [ + "Error while sending request to BENS microservice url: #{url}, body: #{inspect(body, limit: :infinity, printable_limit: :infinity)}: ", + inspect(error, limit: :infinity, printable_limit: :infinity) + ] + end) + + Logger.configure(truncate: old_truncate) + {:error, @request_error_msg} + end + end + + defp http_get_request(url, query_params) do + case HTTPoison.get(url, [], params: query_params) do + {:ok, %Response{body: body, status_code: 200}} -> + Jason.decode(body) + + {_, error} -> + old_truncate = Application.get_env(:logger, :truncate) + Logger.configure(truncate: :infinity) + + Logger.error(fn -> + [ + "Error while sending request to BENS microservice url: #{url}: ", + inspect(error, limit: :infinity, printable_limit: :infinity) + ] + end) + + Logger.configure(truncate: old_truncate) + {:error, @request_error_msg} + end + end + + @spec enabled?() :: boolean + def enabled?, do: Application.get_env(:explorer, __MODULE__)[:enabled] + + defp batch_resolve_name_url do + "#{addresses_url()}:batch-resolve-names" + end + + defp address_lookup_url do + "#{addresses_url()}:lookup" + end + + defp domain_lookup_url do + "#{domains_url()}:lookup" + end + + defp addresses_url do + "#{base_url()}/addresses" + end + + defp domains_url do + "#{base_url()}/domains" + end + + defp base_url do + chain_id = Application.get_env(:block_scout_web, :chain_id) + "#{Microservice.base_url(__MODULE__)}/api/v1/#{chain_id}" + end + + @doc """ + Preload ENS info to list of entities if enabled?() + """ + @spec maybe_preload_ens([supported_types] | supported_types) :: [supported_types] | supported_types + def maybe_preload_ens(argument, function \\ &preload_ens_to_list/1) do + if enabled?() do + function.(argument) + else + argument + end + end + + @spec maybe_preload_ens_info_to_search_results(list()) :: list() + def maybe_preload_ens_info_to_search_results(list) do + maybe_preload_ens(list, &preload_ens_info_to_search_results/1) + end + + @spec maybe_preload_ens_to_transaction(Transaction.t()) :: Transaction.t() + def maybe_preload_ens_to_transaction(transaction) do + maybe_preload_ens(transaction, &preload_ens_to_transaction/1) + end + + @spec preload_ens_to_transaction(Transaction.t()) :: Transaction.t() + def preload_ens_to_transaction(transaction) do + [transaction_with_ens] = preload_ens_to_list([transaction]) + transaction_with_ens + end + + @spec maybe_preload_ens_to_address(Address.t()) :: Address.t() + def maybe_preload_ens_to_address(address) do + maybe_preload_ens(address, &preload_ens_to_address/1) + end + + @spec preload_ens_to_address(Address.t()) :: Address.t() + def preload_ens_to_address(address) do + [address_with_ens] = preload_ens_to_list([address]) + address_with_ens + end + + @doc """ + Preload ENS names to list of entities + """ + @spec preload_ens_to_list([supported_types]) :: [supported_types] + def preload_ens_to_list(items) do + address_hash_strings = + Enum.reduce(items, [], fn item, acc -> + item_to_address_hash_strings(item) ++ acc + end) + + case ens_names_batch_request(address_hash_strings) do + {:ok, result} -> + put_ens_names(result["names"], items) + + _ -> + items + end + end + + @doc """ + Preload ENS info to search result, using address_lookup/1 + """ + @spec preload_ens_info_to_search_results(list) :: list + def preload_ens_info_to_search_results(list) do + Enum.map(list, fn + %{type: "address", ens_info: ens_info} = search_result when not is_nil(ens_info) -> + search_result + + %{type: "address"} = search_result -> + ens_info = search_result[:address_hash] |> address_lookup() |> parse_lookup_response() + Map.put(search_result, :ens_info, ens_info) + + search_result -> + search_result + end) + end + + @spec ens_domain_name_lookup(binary()) :: + nil | %{address_hash: binary(), expiry_date: any(), name: any(), names_count: integer()} + def ens_domain_name_lookup(domain) do + domain |> ens_domain_lookup() |> parse_lookup_response() + end + + defp parse_lookup_response( + {:ok, + %{ + "items" => + [ + %{"name" => name, "expiry_date" => expiry_date, "resolved_address" => %{"hash" => address_hash_string}} + | _other + ] = items + }} + ) do + {:ok, hash} = Chain.string_to_address_hash(address_hash_string) + + %{ + name: name, + expiry_date: expiry_date, + names_count: Enum.count(items), + address_hash: Address.checksum(hash) + } + end + + defp parse_lookup_response(_), do: nil + + defp item_to_address_hash_strings(%Transaction{ + to_address_hash: nil, + created_contract_address_hash: created_contract_address_hash, + from_address_hash: from_address_hash + }) do + [to_string(created_contract_address_hash), to_string(from_address_hash)] + end + + defp item_to_address_hash_strings(%Transaction{ + to_address_hash: to_address_hash, + created_contract_address_hash: nil, + from_address_hash: from_address_hash, + token_transfers: token_transfers + }) do + token_transfers_addresses = + case token_transfers do + token_transfers_list when is_list(token_transfers_list) -> + List.flatten(Enum.map(token_transfers_list, &item_to_address_hash_strings/1)) + + _ -> + [] + end + + [to_string(to_address_hash), to_string(from_address_hash)] ++ token_transfers_addresses + end + + defp item_to_address_hash_strings(%TokenTransfer{ + to_address_hash: to_address_hash, + from_address_hash: from_address_hash + }) do + [to_string(to_address_hash), to_string(from_address_hash)] + end + + defp item_to_address_hash_strings(%InternalTransaction{ + to_address_hash: to_address_hash, + from_address_hash: from_address_hash + }) do + [to_string(to_address_hash), to_string(from_address_hash)] + end + + defp item_to_address_hash_strings(%Log{address_hash: address_hash}) do + [to_string(address_hash)] + end + + defp item_to_address_hash_strings(%Withdrawal{address_hash: address_hash}) do + [to_string(address_hash)] + end + + defp item_to_address_hash_strings(%Block{miner_hash: miner_hash}) do + [to_string(miner_hash)] + end + + defp item_to_address_hash_strings(%CurrentTokenBalance{address_hash: address_hash}) do + [to_string(address_hash)] + end + + defp item_to_address_hash_strings({%Address{} = address, _}) do + item_to_address_hash_strings(address) + end + + defp item_to_address_hash_strings(%Address{hash: hash}) do + [to_string(hash)] + end + + defp put_ens_names(names, items) do + Enum.map(items, &put_ens_name_to_item(&1, names)) + end + + defp put_ens_name_to_item( + %Transaction{ + to_address_hash: to_address_hash, + created_contract_address_hash: created_contract_address_hash, + from_address_hash: from_address_hash + } = tx, + names + ) do + token_transfers = + case tx.token_transfers do + token_transfers_list when is_list(token_transfers_list) -> + Enum.map(token_transfers_list, &put_ens_name_to_item(&1, names)) + + other -> + other + end + + %Transaction{ + tx + | to_address: alter_address(tx.to_address, to_address_hash, names), + created_contract_address: alter_address(tx.created_contract_address, created_contract_address_hash, names), + from_address: alter_address(tx.from_address, from_address_hash, names), + token_transfers: token_transfers + } + end + + defp put_ens_name_to_item( + %TokenTransfer{ + to_address_hash: to_address_hash, + from_address_hash: from_address_hash + } = tt, + names + ) do + %TokenTransfer{ + tt + | to_address: alter_address(tt.to_address, to_address_hash, names), + from_address: alter_address(tt.from_address, from_address_hash, names) + } + end + + defp put_ens_name_to_item( + %InternalTransaction{ + to_address_hash: to_address_hash, + created_contract_address_hash: created_contract_address_hash, + from_address_hash: from_address_hash + } = tx, + names + ) do + %InternalTransaction{ + tx + | to_address: alter_address(tx.to_address, to_address_hash, names), + created_contract_address: alter_address(tx.created_contract_address, created_contract_address_hash, names), + from_address: alter_address(tx.from_address, from_address_hash, names) + } + end + + defp put_ens_name_to_item(%Log{address_hash: address_hash} = log, names) do + %Log{log | address: alter_address(log.address, address_hash, names)} + end + + defp put_ens_name_to_item(%Withdrawal{address_hash: address_hash} = withdrawal, names) do + %Withdrawal{withdrawal | address: alter_address(withdrawal.address, address_hash, names)} + end + + defp put_ens_name_to_item(%Block{miner_hash: miner_hash} = block, names) do + %Block{block | miner: alter_address(block.miner, miner_hash, names)} + end + + defp put_ens_name_to_item(%CurrentTokenBalance{address_hash: address_hash} = current_token_balance, names) do + %CurrentTokenBalance{ + current_token_balance + | address: alter_address(current_token_balance.address, address_hash, names) + } + end + + defp put_ens_name_to_item({%Address{} = address, count}, names) do + {put_ens_name_to_item(address, names), count} + end + + defp put_ens_name_to_item(%Address{} = address, names) do + alter_address(address, address.hash, names) + end + + defp alter_address(_, nil, _names) do + nil + end + + defp alter_address(%NotLoaded{}, address_hash, names) do + %{ens_domain_name: names[to_string(address_hash)]} + end + + defp alter_address(%Address{} = address, address_hash, names) do + %Address{address | ens_domain_name: names[to_string(address_hash)]} + end +end diff --git a/apps/explorer/lib/explorer/migrator/address_current_token_balance_token_type.ex b/apps/explorer/lib/explorer/migrator/address_current_token_balance_token_type.ex new file mode 100644 index 000000000000..93226db73fce --- /dev/null +++ b/apps/explorer/lib/explorer/migrator/address_current_token_balance_token_type.ex @@ -0,0 +1,51 @@ +defmodule Explorer.Migrator.AddressCurrentTokenBalanceTokenType do + @moduledoc """ + Fill empty token_type's for address_current_token_balances + """ + + use Explorer.Migrator.FillingMigration + + import Ecto.Query + + alias Explorer.Chain.Address.CurrentTokenBalance + alias Explorer.Chain.Cache.BackgroundMigrations + alias Explorer.Migrator.FillingMigration + alias Explorer.Repo + + @migration_name "ctb_token_type" + + @impl FillingMigration + def migration_name, do: @migration_name + + @impl FillingMigration + def last_unprocessed_identifiers do + limit = batch_size() * concurrency() + + unprocessed_data_query() + |> select([ctb], ctb.id) + |> limit(^limit) + |> Repo.all(timeout: :infinity) + end + + @impl FillingMigration + def unprocessed_data_query do + from(ctb in CurrentTokenBalance, where: is_nil(ctb.token_type)) + end + + @impl FillingMigration + def update_batch(token_balance_ids) do + query = + from(current_token_balance in CurrentTokenBalance, + join: token in assoc(current_token_balance, :token), + where: current_token_balance.id in ^token_balance_ids, + update: [set: [token_type: token.type]] + ) + + Repo.update_all(query, [], timeout: :infinity) + end + + @impl FillingMigration + def update_cache do + BackgroundMigrations.set_ctb_token_type_finished(true) + end +end diff --git a/apps/explorer/lib/explorer/migrator/address_token_balance_token_type.ex b/apps/explorer/lib/explorer/migrator/address_token_balance_token_type.ex new file mode 100644 index 000000000000..9427db73ed60 --- /dev/null +++ b/apps/explorer/lib/explorer/migrator/address_token_balance_token_type.ex @@ -0,0 +1,51 @@ +defmodule Explorer.Migrator.AddressTokenBalanceTokenType do + @moduledoc """ + Fill empty token_type's for address_token_balances + """ + + use Explorer.Migrator.FillingMigration + + import Ecto.Query + + alias Explorer.Chain.Address.TokenBalance + alias Explorer.Chain.Cache.BackgroundMigrations + alias Explorer.Migrator.FillingMigration + alias Explorer.Repo + + @migration_name "tb_token_type" + + @impl FillingMigration + def migration_name, do: @migration_name + + @impl FillingMigration + def last_unprocessed_identifiers do + limit = batch_size() * concurrency() + + unprocessed_data_query() + |> select([tb], tb.id) + |> limit(^limit) + |> Repo.all(timeout: :infinity) + end + + @impl FillingMigration + def unprocessed_data_query do + from(tb in TokenBalance, where: is_nil(tb.token_type)) + end + + @impl FillingMigration + def update_batch(token_balance_ids) do + query = + from(token_balance in TokenBalance, + join: token in assoc(token_balance, :token), + where: token_balance.id in ^token_balance_ids, + update: [set: [token_type: token.type]] + ) + + Repo.update_all(query, [], timeout: :infinity) + end + + @impl FillingMigration + def update_cache do + BackgroundMigrations.set_tb_token_type_finished(true) + end +end diff --git a/apps/explorer/lib/explorer/migrator/filling_migration.ex b/apps/explorer/lib/explorer/migrator/filling_migration.ex new file mode 100644 index 000000000000..507dfcb6e5f7 --- /dev/null +++ b/apps/explorer/lib/explorer/migrator/filling_migration.ex @@ -0,0 +1,84 @@ +defmodule Explorer.Migrator.FillingMigration do + @moduledoc """ + Template for creating migrations that fills some fields in existing entities + """ + + @callback migration_name :: String.t() + @callback unprocessed_data_query :: Ecto.Query.t() + @callback last_unprocessed_identifiers :: [any()] + @callback update_batch([any()]) :: any() + @callback update_cache :: any() + + defmacro __using__(_opts) do + quote do + @behaviour Explorer.Migrator.FillingMigration + + use GenServer, restart: :transient + + import Ecto.Query + + alias Explorer.Migrator.MigrationStatus + alias Explorer.Repo + + @default_batch_size 500 + + def start_link(_) do + GenServer.start_link(__MODULE__, :ok, name: __MODULE__) + end + + def migration_finished? do + MigrationStatus.get_status(migration_name()) == "completed" + end + + @impl true + def init(_) do + case MigrationStatus.get_status(migration_name()) do + "completed" -> + update_cache() + :ignore + + _ -> + MigrationStatus.set_status(migration_name(), "started") + schedule_batch_migration() + {:ok, %{}} + end + end + + @impl true + def handle_info(:migrate_batch, state) do + case last_unprocessed_identifiers() do + [] -> + update_cache() + MigrationStatus.set_status(migration_name(), "completed") + {:stop, :normal, state} + + hashes -> + hashes + |> Enum.chunk_every(batch_size()) + |> Enum.map(&run_task/1) + |> Task.await_many(:infinity) + + schedule_batch_migration() + + {:noreply, state} + end + end + + defp run_task(batch), do: Task.async(fn -> update_batch(batch) end) + + defp schedule_batch_migration do + Process.send(self(), :migrate_batch, []) + end + + defp batch_size do + Application.get_env(:explorer, __MODULE__)[:batch_size] || @default_batch_size + end + + defp concurrency do + default = 4 * System.schedulers_online() + + Application.get_env(:explorer, __MODULE__)[:concurrency] || default + end + end + end +end diff --git a/apps/explorer/lib/explorer/migrator/migration_status.ex b/apps/explorer/lib/explorer/migrator/migration_status.ex new file mode 100644 index 000000000000..01a7bbd54084 --- /dev/null +++ b/apps/explorer/lib/explorer/migrator/migration_status.ex @@ -0,0 +1,32 @@ +defmodule Explorer.Migrator.MigrationStatus do + @moduledoc """ + Module is responsible for keeping the current status of background migrations. + """ + use Explorer.Schema + + alias Explorer.Repo + + @primary_key false + schema "migrations_status" do + field(:migration_name, :string) + # ["started", "completed"] + field(:status, :string) + + timestamps() + end + + @doc false + def changeset(migration_status \\ %__MODULE__{}, params) do + cast(migration_status, params, [:migration_name, :status]) + end + + def get_status(migration_name) do + Repo.one(from(ms in __MODULE__, where: ms.migration_name == ^migration_name, select: ms.status)) + end + + def set_status(migration_name, status) do + %{migration_name: migration_name, status: status} + |> changeset() + |> Repo.insert(on_conflict: {:replace_all_except, [:inserted_at]}, conflict_target: :migration_name) + end +end diff --git a/apps/explorer/lib/explorer/migrator/transactions_denormalization.ex b/apps/explorer/lib/explorer/migrator/transactions_denormalization.ex new file mode 100644 index 000000000000..fe66548aba14 --- /dev/null +++ b/apps/explorer/lib/explorer/migrator/transactions_denormalization.ex @@ -0,0 +1,53 @@ +defmodule Explorer.Migrator.TransactionsDenormalization do + @moduledoc """ + Migrates all transactions to have set block_consensus and block_timestamp + """ + + use Explorer.Migrator.FillingMigration + + import Ecto.Query + + alias Explorer.Chain.Cache.BackgroundMigrations + alias Explorer.Chain.Transaction + alias Explorer.Migrator.FillingMigration + alias Explorer.Repo + + @migration_name "denormalization" + + @impl FillingMigration + def migration_name, do: @migration_name + + @impl FillingMigration + def last_unprocessed_identifiers do + limit = batch_size() * concurrency() + + unprocessed_data_query() + |> select([t], t.hash) + |> limit(^limit) + |> Repo.all(timeout: :infinity) + end + + @impl FillingMigration + def unprocessed_data_query do + from(t in Transaction, + where: not is_nil(t.block_hash) and (is_nil(t.block_consensus) or is_nil(t.block_timestamp)) + ) + end + + @impl FillingMigration + def update_batch(transaction_hashes) do + query = + from(transaction in Transaction, + join: block in assoc(transaction, :block), + where: transaction.hash in ^transaction_hashes, + update: [set: [block_consensus: block.consensus, block_timestamp: block.timestamp]] + ) + + Repo.update_all(query, [], timeout: :infinity) + end + + @impl FillingMigration + def update_cache do + BackgroundMigrations.set_denormalization_finished(true) + end +end diff --git a/apps/explorer/lib/explorer/smart_contract/compiler_version.ex b/apps/explorer/lib/explorer/smart_contract/compiler_version.ex index e3d2bcde8a07..3a0e898ff5e2 100644 --- a/apps/explorer/lib/explorer/smart_contract/compiler_version.ex +++ b/apps/explorer/lib/explorer/smart_contract/compiler_version.ex @@ -31,36 +31,25 @@ defmodule Explorer.SmartContract.CompilerVersion do end defp fetch_solc_versions do - if RustVerifierInterface.enabled?() do - RustVerifierInterface.get_versions_list() - else - headers = [{"Content-Type", "application/json"}] - - case HTTPoison.get(source_url(:solc), headers) do - {:ok, %{status_code: 200, body: body}} -> - {:ok, format_data(body, :solc)} - - {:ok, %{status_code: _status_code, body: body}} -> - {:error, decode_json(body)["error"]} - - {:error, %{reason: reason}} -> - {:error, reason} - end - end + fetch_compiler_versions(&RustVerifierInterface.get_versions_list/0, :solc) end defp fetch_vyper_versions do + fetch_compiler_versions(&RustVerifierInterface.vyper_get_versions_list/0, :vyper) + end + + defp fetch_compiler_versions(compiler_list_fn, compiler_type) do if RustVerifierInterface.enabled?() do - RustVerifierInterface.vyper_get_versions_list() + compiler_list_fn.() else headers = [{"Content-Type", "application/json"}] - case HTTPoison.get(source_url(:vyper), headers) do + case HTTPoison.get(source_url(compiler_type), headers) do {:ok, %{status_code: 200, body: body}} -> - {:ok, format_data(body, :vyper)} + {:ok, format_data(body, compiler_type)} {:ok, %{status_code: _status_code, body: body}} -> - {:error, decode_json(body)["error"]} + {:error, Helper.decode_json(body)["error"]} {:error, %{reason: reason}} -> {:error, reason} @@ -140,10 +129,6 @@ defmodule Explorer.SmartContract.CompilerVersion do end end - defp decode_json(json) do - Jason.decode!(json) - end - @spec source_url(:solc | :vyper) :: String.t() defp source_url(compiler) do case compiler do diff --git a/apps/explorer/lib/explorer/smart_contract/rust_verifier_interface_behaviour.ex b/apps/explorer/lib/explorer/smart_contract/rust_verifier_interface_behaviour.ex index 2466e6e1d572..2c5629d219c0 100644 --- a/apps/explorer/lib/explorer/smart_contract/rust_verifier_interface_behaviour.ex +++ b/apps/explorer/lib/explorer/smart_contract/rust_verifier_interface_behaviour.ex @@ -5,7 +5,7 @@ defmodule Explorer.SmartContract.RustVerifierInterfaceBehaviour do defmacro __using__(_) do # credo:disable-for-next-line quote([]) do - alias Explorer.Utility.RustService + alias Explorer.Utility.Microservice alias HTTPoison.Response require Logger @@ -172,7 +172,7 @@ defmodule Explorer.SmartContract.RustVerifierInterfaceBehaviour do def base_api_url, do: "#{base_url()}" <> "/api/v2" def base_url do - RustService.base_url(Explorer.SmartContract.RustVerifierInterfaceBehaviour) + Microservice.base_url(Explorer.SmartContract.RustVerifierInterfaceBehaviour) end def enabled?, do: Application.get_env(:explorer, Explorer.SmartContract.RustVerifierInterfaceBehaviour)[:enabled] diff --git a/apps/explorer/lib/explorer/smart_contract/sig_provider_interface.ex b/apps/explorer/lib/explorer/smart_contract/sig_provider_interface.ex index 92bba0d0ec70..b97f9bcd1edf 100644 --- a/apps/explorer/lib/explorer/smart_contract/sig_provider_interface.ex +++ b/apps/explorer/lib/explorer/smart_contract/sig_provider_interface.ex @@ -3,7 +3,7 @@ defmodule Explorer.SmartContract.SigProviderInterface do Adapter for decoding events and function calls with https://github.com/blockscout/blockscout-rs/tree/main/sig-provider """ - alias Explorer.Utility.RustService + alias Explorer.Utility.Microservice alias HTTPoison.Response require Logger @@ -81,7 +81,7 @@ defmodule Explorer.SmartContract.SigProviderInterface do def base_api_url, do: "#{base_url()}" <> "/api/v1/abi" def base_url do - RustService.base_url(__MODULE__) + Microservice.base_url(__MODULE__) end def enabled?, do: Application.get_env(:explorer, __MODULE__)[:enabled] diff --git a/apps/explorer/lib/explorer/sorting_helper.ex b/apps/explorer/lib/explorer/sorting_helper.ex new file mode 100644 index 000000000000..f6a478d85d78 --- /dev/null +++ b/apps/explorer/lib/explorer/sorting_helper.ex @@ -0,0 +1,168 @@ +defmodule Explorer.SortingHelper do + @moduledoc """ + Module that order and paginate queries dynamically based on default and provided sorting parameters. + Example of sorting parameters: + ``` + [{:asc, :fetched_coin_balance, :address}, {:dynamic, :contract_code_size, :desc, dynamic([t], fragment(LENGTH(?), t.contract_source_code))}, desc: :id] + ``` + First list entry specify joined address table column as a column to order by and paginate, second entry + specifies name of a key in paging_options and arbitrary dynamic that will be used in ordering and pagination, + third entry specifies own column name to order by and paginate. + """ + require Explorer.SortingHelper + + alias Explorer.PagingOptions + + import Ecto.Query + + @typep ordering :: :asc | :asc_nulls_first | :asc_nulls_last | :desc | :desc_nulls_first | :desc_nulls_last + @typep column :: atom + @typep binding :: atom + @type sorting_params :: [ + {ordering, column} | {ordering, column, binding} | {:dynamic, column, ordering, Ecto.Query.dynamic_expr()} + ] + + @doc """ + Applies sorting to query based on default sorting params and sorting params from the client, + these params merged keeping provided one over default one. + """ + @spec apply_sorting(Ecto.Query.t(), sorting_params, sorting_params) :: Ecto.Query.t() + def apply_sorting(query, sorting, default_sorting) when is_list(sorting) and is_list(default_sorting) do + sorting |> merge_sorting_params_with_defaults(default_sorting) |> sorting_params_to_order_by(query) + end + + defp merge_sorting_params_with_defaults([], default_sorting) when is_list(default_sorting), do: default_sorting + + defp merge_sorting_params_with_defaults(sorting, default_sorting) + when is_list(sorting) and is_list(default_sorting) do + (sorting ++ default_sorting) + |> Enum.uniq_by(fn + {_, field} -> field + {_, field, as} -> {field, as} + {:dynamic, key_name, _, _} -> key_name + end) + end + + defp sorting_params_to_order_by(sorting_params, query) do + sorting_params + |> Enum.reduce(query, fn + {:dynamic, _key_name, order, dynamic}, query -> query |> order_by(^[{order, dynamic}]) + {order, column, binding}, query -> query |> order_by([{^order, field(as(^binding), ^column)}]) + {order, column}, query -> query |> order_by(^[{order, column}]) + end) + end + + @doc """ + Page the query based on paging options, default sorting params and sorting params from the client, + these params merged keeping provided one over default one. + """ + @spec page_with_sorting(Ecto.Query.t(), PagingOptions.t(), sorting_params, sorting_params) :: Ecto.Query.t() + def page_with_sorting(query, %PagingOptions{key: key, page_size: page_size}, sorting, default_sorting) + when not is_nil(key) do + sorting + |> merge_sorting_params_with_defaults(default_sorting) + |> do_page_with_sorting() + |> case do + nil -> query + dynamic_where -> query |> where(^dynamic_where.(key)) + end + |> limit_query(page_size) + end + + def page_with_sorting(query, %PagingOptions{page_size: page_size}, _sorting, _default_sorting) do + query |> limit_query(page_size) + end + + def page_with_sorting(query, _, _sorting, _default_sorting), do: query + + defp limit_query(query, limit) when is_integer(limit), do: query |> limit(^limit) + defp limit_query(query, _), do: query + + defp do_page_with_sorting([{order, column} | rest]) do + fn key -> page_by_column(key, column, order, do_page_with_sorting(rest)) end + end + + defp do_page_with_sorting([{:dynamic, key_name, order, dynamic} | rest]) do + fn key -> page_by_column(key, {:dynamic, key_name, dynamic}, order, do_page_with_sorting(rest)) end + end + + defp do_page_with_sorting([{order, column, binding} | rest]) do + fn key -> page_by_column(key, {column, binding}, order, do_page_with_sorting(rest)) end + end + + defp do_page_with_sorting([]), do: nil + + for {key_name, pattern, ecto_value} <- [ + {quote(do: key_name), quote(do: {:dynamic, key_name, dynamic}), quote(do: ^dynamic)}, + {quote(do: column), quote(do: {column, binding}), quote(do: field(as(^binding), ^column))}, + {quote(do: column), quote(do: column), quote(do: field(t, ^column))} + ] do + defp page_by_column(key, unquote(pattern), :desc_nulls_last, next_column) do + case key[unquote(key_name)] do + nil -> + dynamic([t], is_nil(unquote(ecto_value)) and ^apply_next_column(next_column, key)) + + value -> + dynamic( + [t], + is_nil(unquote(ecto_value)) or unquote(ecto_value) < ^value or + (unquote(ecto_value) == ^value and ^apply_next_column(next_column, key)) + ) + end + end + + defp page_by_column(key, unquote(pattern), :asc_nulls_first, next_column) do + case key[unquote(key_name)] do + nil -> + dynamic([t], not is_nil(unquote(ecto_value)) or ^apply_next_column(next_column, key)) + + value -> + dynamic( + [t], + not is_nil(unquote(ecto_value)) and + (unquote(ecto_value) > ^value or + (unquote(ecto_value) == ^value and ^apply_next_column(next_column, key))) + ) + end + end + + defp page_by_column(key, unquote(pattern), order, next_column) when order in ~w(asc asc_nulls_last)a do + case key[unquote(key_name)] do + nil -> + dynamic([t], is_nil(unquote(ecto_value)) and ^apply_next_column(next_column, key)) + + value -> + dynamic( + [t], + is_nil(unquote(ecto_value)) or + (unquote(ecto_value) > ^value or + (unquote(ecto_value) == ^value and ^apply_next_column(next_column, key))) + ) + end + end + + defp page_by_column(key, unquote(pattern), order, next_column) + when order in ~w(desc desc_nulls_first)a do + case key[unquote(key_name)] do + nil -> + dynamic([t], not is_nil(unquote(ecto_value)) or ^apply_next_column(next_column, key)) + + value -> + dynamic( + [t], + not is_nil(unquote(ecto_value)) and + (unquote(ecto_value) < ^value or + (unquote(ecto_value) == ^value and ^apply_next_column(next_column, key))) + ) + end + end + end + + defp apply_next_column(nil, _key) do + false + end + + defp apply_next_column(next_column, key) do + next_column.(key) + end +end diff --git a/apps/explorer/lib/explorer/third_party_integrations/solidityscan.ex b/apps/explorer/lib/explorer/third_party_integrations/solidityscan.ex new file mode 100644 index 000000000000..40a5bffb9784 --- /dev/null +++ b/apps/explorer/lib/explorer/third_party_integrations/solidityscan.ex @@ -0,0 +1,40 @@ +defmodule Explorer.ThirdPartyIntegrations.SolidityScan do + @moduledoc """ + Module for SolidityScan integration https://apidoc.solidityscan.com/solidityscan-security-api/solidityscan-other-apis/quickscan-api-v1 + """ + + alias Explorer.Helper + + @blockscout_platform_id "16" + @recv_timeout 60_000 + + @doc """ + Proxy request to solidityscan API endpoint for the given smart-contract + """ + @spec solidityscan_request(String.t()) :: any() + def solidityscan_request(address_hash_string) do + headers = [{"Authorization", "Token #{api_key()}"}] + + url = base_url(address_hash_string) + + case HTTPoison.get(url, headers, recv_timeout: @recv_timeout) do + {:ok, %HTTPoison.Response{status_code: 200, body: body}} -> + Helper.decode_json(body) + + _ -> + nil + end + end + + defp base_url(address_hash_string) do + "https://api.solidityscan.com/api/v1/quickscan/#{@blockscout_platform_id}/#{chain_id()}/#{address_hash_string}" + end + + defp chain_id do + Application.get_env(:explorer, __MODULE__)[:chain_id] + end + + defp api_key do + Application.get_env(:explorer, __MODULE__)[:api_key] + end +end diff --git a/apps/explorer/lib/explorer/third_party_integrations/sourcify.ex b/apps/explorer/lib/explorer/third_party_integrations/sourcify.ex index b510fb355d49..605a50547ebd 100644 --- a/apps/explorer/lib/explorer/third_party_integrations/sourcify.ex +++ b/apps/explorer/lib/explorer/third_party_integrations/sourcify.ex @@ -4,6 +4,7 @@ defmodule Explorer.ThirdPartyIntegrations.Sourcify do """ use Tesla + alias Explorer.Helper, as: ExplorerHelper alias Explorer.SmartContract.{Helper, RustVerifierInterface} alias HTTPoison.{Error, Response} alias Tesla.Multipart @@ -223,7 +224,7 @@ defmodule Explorer.ThirdPartyIntegrations.Sourcify do end defp parse_verify_http_response(body) do - body_json = decode_json(body) + body_json = ExplorerHelper.decode_json(body) case body_json do # Success status from native Sourcify server @@ -246,7 +247,7 @@ defmodule Explorer.ThirdPartyIntegrations.Sourcify do end defp parse_check_by_address_http_response(body) do - body_json = decode_json(body) + body_json = ExplorerHelper.decode_json(body) case body_json do [%{"status" => "perfect"}] -> @@ -264,11 +265,11 @@ defmodule Explorer.ThirdPartyIntegrations.Sourcify do end defp parse_get_metadata_http_response(body) do - body_json = decode_json(body) + body_json = ExplorerHelper.decode_json(body) case body_json do %{"message" => message, "errors" => errors} -> - {:error, "#{message}: #{decode_json(errors)}"} + {:error, "#{message}: #{ExplorerHelper.decode_json(errors)}"} metadata -> {:ok, metadata} @@ -276,11 +277,11 @@ defmodule Explorer.ThirdPartyIntegrations.Sourcify do end defp parse_get_metadata_any_http_response(body) do - body_json = decode_json(body) + body_json = ExplorerHelper.decode_json(body) case body_json do %{"message" => message, "errors" => errors} -> - {:error, "#{message}: #{decode_json(errors)}"} + {:error, "#{message}: #{ExplorerHelper.decode_json(errors)}"} %{"status" => status, "files" => metadata} -> {:ok, status, metadata} @@ -290,16 +291,23 @@ defmodule Explorer.ThirdPartyIntegrations.Sourcify do end end + @invalid_json_response "invalid http error json response" defp parse_http_error_response(body) do - body_json = decode_json(body) + body_json = ExplorerHelper.decode_json(body) if is_map(body_json) do - {:error, body_json["error"]} + error = body_json["error"] + + parse_http_error_response_internal(error) else - {:error, body} + parse_http_error_response_internal(body) end end + defp parse_http_error_response_internal(nil), do: {:error, @invalid_json_response} + + defp parse_http_error_response_internal(data), do: {:error, data} + def parse_params_from_sourcify(address_hash_string, verification_metadata) do filtered_files = verification_metadata @@ -350,7 +358,7 @@ defmodule Explorer.ThirdPartyIntegrations.Sourcify do defp parse_json_from_sourcify_for_insertion(verification_metadata_json) do %{"name" => _, "content" => content} = verification_metadata_json - content_json = decode_json(content) + content_json = ExplorerHelper.decode_json(content) compiler_version = "v" <> (content_json |> Map.get("compiler") |> Map.get("version")) abi = content_json |> Map.get("output") |> Map.get("abi") settings = Map.get(content_json, "settings") @@ -401,12 +409,6 @@ defmodule Explorer.ThirdPartyIntegrations.Sourcify do |> Map.put("contract_source_code", content) end - def decode_json(data) do - Jason.decode!(data) - rescue - _ -> data - end - defp config(module, key) do :explorer |> Application.get_env(module) diff --git a/apps/explorer/lib/explorer/token_instance_owner_address_migration/helper.ex b/apps/explorer/lib/explorer/token_instance_owner_address_migration/helper.ex index 7b0a092245f0..01bc78d39e28 100644 --- a/apps/explorer/lib/explorer/token_instance_owner_address_migration/helper.ex +++ b/apps/explorer/lib/explorer/token_instance_owner_address_migration/helper.ex @@ -34,38 +34,45 @@ defmodule Explorer.TokenInstanceOwnerAddressMigration.Helper do | {:error, any, any, map} def fetch_and_insert(batch) do changes = - Enum.map(batch, fn %{token_id: token_id, token_contract_address_hash: token_contract_address_hash} -> - token_transfer_query = - from(tt in TokenTransfer.only_consensus_transfers_query(), - where: - tt.token_contract_address_hash == ^token_contract_address_hash and - fragment("? @> ARRAY[?::decimal]", tt.token_ids, ^token_id), - order_by: [desc: tt.block_number, desc: tt.log_index], - limit: 1, - select: %{ - token_contract_address_hash: tt.token_contract_address_hash, - token_ids: tt.token_ids, - to_address_hash: tt.to_address_hash, - block_number: tt.block_number, - log_index: tt.log_index - } - ) + batch + |> Enum.map(&process_instance/1) + |> Enum.reject(&is_nil/1) - token_transfer = - Repo.one(token_transfer_query) || - %{to_address_hash: @burn_address_hash, block_number: -1, log_index: -1} + Chain.import(%{token_instances: %{params: changes}}) + end - %{ - token_contract_address_hash: token_contract_address_hash, - token_id: token_id, - token_type: "ERC-721", - owner_address_hash: token_transfer.to_address_hash, - owner_updated_at_block: token_transfer.block_number, - owner_updated_at_log_index: token_transfer.log_index + defp process_instance(%{token_id: token_id, token_contract_address_hash: token_contract_address_hash}) do + token_transfer_query = + from(tt in TokenTransfer.only_consensus_transfers_query(), + where: + tt.token_contract_address_hash == ^token_contract_address_hash and + fragment("? @> ARRAY[?::decimal]", tt.token_ids, ^token_id), + order_by: [desc: tt.block_number, desc: tt.log_index], + limit: 1, + select: %{ + token_contract_address_hash: tt.token_contract_address_hash, + token_ids: tt.token_ids, + to_address_hash: tt.to_address_hash, + block_number: tt.block_number, + log_index: tt.log_index } - end) + ) - Chain.import(%{token_instances: %{params: changes}}) + token_transfer = + Repo.one(token_transfer_query, timeout: :timer.minutes(5)) || + %{to_address_hash: @burn_address_hash, block_number: -1, log_index: -1} + + %{ + token_contract_address_hash: token_contract_address_hash, + token_id: token_id, + token_type: "ERC-721", + owner_address_hash: token_transfer.to_address_hash, + owner_updated_at_block: token_transfer.block_number, + owner_updated_at_log_index: token_transfer.log_index + } + rescue + DBConnection.ConnectionError -> + nil end @spec unfilled_token_instances_exists? :: boolean diff --git a/apps/explorer/lib/explorer/token_transfer_token_id_migration/lowest_block_number_updater.ex b/apps/explorer/lib/explorer/token_transfer_token_id_migration/lowest_block_number_updater.ex deleted file mode 100644 index 5794e524ccfc..000000000000 --- a/apps/explorer/lib/explorer/token_transfer_token_id_migration/lowest_block_number_updater.ex +++ /dev/null @@ -1,65 +0,0 @@ -defmodule Explorer.TokenTransferTokenIdMigration.LowestBlockNumberUpdater do - @moduledoc """ - Collects processed block numbers from token id migration workers - and updates last_processed_block_number according to them. - Full algorithm is in the 'Indexer.Fetcher.TokenTransferTokenIdMigration.Supervisor' module doc. - """ - use GenServer - - alias Explorer.Utility.TokenTransferTokenIdMigratorProgress - - def start_link(_) do - GenServer.start_link(__MODULE__, :ok, name: __MODULE__) - end - - @impl true - def init(_) do - last_processed_block_number = TokenTransferTokenIdMigratorProgress.get_last_processed_block_number() - - {:ok, %{last_processed_block_number: last_processed_block_number, processed_ranges: []}} - end - - def add_range(from, to) do - GenServer.cast(__MODULE__, {:add_range, from..to}) - end - - @impl true - def handle_cast({:add_range, range}, %{processed_ranges: processed_ranges} = state) do - ranges = - [range | processed_ranges] - |> Enum.sort_by(& &1.last, &>=/2) - |> normalize_ranges() - - {new_last_number, new_ranges} = maybe_update_last_processed_number(state.last_processed_block_number, ranges) - - {:noreply, %{last_processed_block_number: new_last_number, processed_ranges: new_ranges}} - end - - defp normalize_ranges(ranges) do - %{prev_range: prev, result: result} = - Enum.reduce(ranges, %{prev_range: nil, result: []}, fn range, %{prev_range: prev_range, result: result} -> - case {prev_range, range} do - {nil, _} -> - %{prev_range: range, result: result} - - {%{last: l1} = r1, %{first: f2} = r2} when l1 - 1 > f2 -> - %{prev_range: r2, result: [r1 | result]} - - {%{first: f1}, %{last: l2}} -> - %{prev_range: f1..l2, result: result} - end - end) - - Enum.reverse([prev | result]) - end - - # since ranges are normalized, we need to check only the first range to determine the new last_processed_number - defp maybe_update_last_processed_number(current_last, [from..to | rest] = ranges) when current_last - 1 <= from do - case TokenTransferTokenIdMigratorProgress.update_last_processed_block_number(to) do - {:ok, _} -> {to, rest} - _ -> {current_last, ranges} - end - end - - defp maybe_update_last_processed_number(current_last, ranges), do: {current_last, ranges} -end diff --git a/apps/explorer/lib/explorer/token_transfer_token_id_migration/supervisor.ex b/apps/explorer/lib/explorer/token_transfer_token_id_migration/supervisor.ex deleted file mode 100644 index 2121158fee35..000000000000 --- a/apps/explorer/lib/explorer/token_transfer_token_id_migration/supervisor.ex +++ /dev/null @@ -1,70 +0,0 @@ -defmodule Explorer.TokenTransferTokenIdMigration.Supervisor do - @moduledoc """ - Supervises parts of token id migration process. - - Migration process algorithm: - - Defining the bounds of migration (by the first and the last block number of TokenTransfer). - Supervisor starts the workers in amount equal to 'TOKEN_ID_MIGRATION_CONCURRENCY' env value (defaults to 1) - and the 'LowestBlockNumberUpdater'. - - Each worker goes through the token transfers by batches ('TOKEN_ID_MIGRATION_BATCH_SIZE', defaults to 500) - and updates the token_ids to be equal of [token_id] for transfers that has any token_id. - Worker goes from the newest blocks to latest. - After worker is done with current batch, it sends the information about processed batch to 'LowestBlockNumberUpdater' - and takes the next by defining its bounds based on amount of all workers. - - For example, if batch size is 10, we have 5 workers and 100 items to be processed, - the distribution will be like this: - 1 worker - 99..90, 49..40 - 2 worker - 89..80, 39..30 - 3 worker - 79..70, 29..20 - 4 worker - 69..60, 19..10 - 5 worker - 59..50, 9..0 - - 'LowestBlockNumberUpdater' keeps the information about the last processed block number - (which is stored in the 'token_transfer_token_id_migrator_progress' db entity) - and block ranges that has already been processed by the workers but couldn't be committed - to last processed block number yet (because of the possible gap between the current last block - and upper bound of the last processed batch). Uncommitted block numbers are stored in normalize ranges. - When there is no gap between the last processed block number and the upper bound of the upper range, - 'LowestBlockNumberUpdater' updates the last processed block number in db and drops this range from its state. - - This supervisor won't start if the migration is completed - (last processed block number in db == 'TOKEN_ID_MIGRATION_FIRST_BLOCK' (defaults to 0)). - """ - use Supervisor - - alias Explorer.TokenTransferTokenIdMigration.{LowestBlockNumberUpdater, Worker} - alias Explorer.Utility.TokenTransferTokenIdMigratorProgress - - @default_first_block 0 - @default_workers_count 1 - - def start_link(_) do - Supervisor.start_link(__MODULE__, :ok, name: __MODULE__) - end - - @impl true - def init(_) do - first_block = Application.get_env(:explorer, :token_id_migration)[:first_block] || @default_first_block - last_block = TokenTransferTokenIdMigratorProgress.get_last_processed_block_number() - - if last_block > first_block do - workers_count = Application.get_env(:explorer, :token_id_migration)[:concurrency] || @default_workers_count - - workers = - Enum.map(1..workers_count, fn id -> - Supervisor.child_spec( - {Worker, idx: id, first_block: first_block, last_block: last_block, step: workers_count - 1}, - id: {Worker, id}, - restart: :transient - ) - end) - - Supervisor.init([LowestBlockNumberUpdater | workers], strategy: :one_for_one) - else - :ignore - end - end -end diff --git a/apps/explorer/lib/explorer/token_transfer_token_id_migration/worker.ex b/apps/explorer/lib/explorer/token_transfer_token_id_migration/worker.ex deleted file mode 100644 index f7916ed0582a..000000000000 --- a/apps/explorer/lib/explorer/token_transfer_token_id_migration/worker.ex +++ /dev/null @@ -1,84 +0,0 @@ -defmodule Explorer.TokenTransferTokenIdMigration.Worker do - @moduledoc """ - Performs the migration of TokenTransfer token_id to token_ids by batches. - Full algorithm is in the 'Explorer.TokenTransferTokenIdMigration.Supervisor' module doc. - """ - use GenServer - - import Ecto.Query - - alias Explorer.Chain.TokenTransfer - alias Explorer.Repo - alias Explorer.TokenTransferTokenIdMigration.LowestBlockNumberUpdater - - @default_batch_size 500 - @interval 10 - - def start_link(idx: idx, first_block: first, last_block: last, step: step) do - GenServer.start_link(__MODULE__, %{idx: idx, bottom_block: first, last_block: last, step: step}) - end - - @impl true - def init(%{idx: idx, bottom_block: bottom_block, last_block: last_block, step: step}) do - batch_size = Application.get_env(:explorer, :token_id_migration)[:batch_size] || @default_batch_size - range = calculate_new_range(last_block, bottom_block, batch_size, idx - 1) - - schedule_next_update() - - {:ok, %{batch_size: batch_size, bottom_block: bottom_block, step: step, current_range: range}} - end - - @impl true - def handle_info(:update, %{current_range: :out_of_bound} = state) do - {:stop, :normal, state} - end - - @impl true - def handle_info(:update, %{current_range: {lower_bound, upper_bound}} = state) do - case do_update(lower_bound, upper_bound) do - true -> - LowestBlockNumberUpdater.add_range(upper_bound, lower_bound) - new_range = calculate_new_range(lower_bound, state.bottom_block, state.batch_size, state.step) - schedule_next_update() - {:noreply, %{state | current_range: new_range}} - - _ -> - schedule_next_update() - {:noreply, state} - end - end - - defp calculate_new_range(last_processed_block, bottom_block, batch_size, step) do - upper_bound = last_processed_block - step * batch_size - 1 - lower_bound = max(upper_bound - batch_size + 1, bottom_block) - - if upper_bound >= bottom_block do - {lower_bound, upper_bound} - else - :out_of_bound - end - end - - defp do_update(lower_bound, upper_bound) do - token_transfers_batch_query = - from( - tt in TokenTransfer, - where: tt.block_number >= ^lower_bound, - where: tt.block_number <= ^upper_bound - ) - - token_transfers_batch_query - |> Repo.all() - |> Enum.filter(fn %{token_id: token_id} -> not is_nil(token_id) end) - |> Enum.map(fn token_transfer -> - token_transfer - |> TokenTransfer.changeset(%{token_ids: [token_transfer.token_id], token_id: nil}) - |> Repo.update() - end) - |> Enum.all?(&match?({:ok, _}, &1)) - end - - defp schedule_next_update do - Process.send_after(self(), :update, @interval) - end -end diff --git a/apps/explorer/lib/explorer/utility/microservice.ex b/apps/explorer/lib/explorer/utility/microservice.ex new file mode 100644 index 000000000000..fe1af3df46b4 --- /dev/null +++ b/apps/explorer/lib/explorer/utility/microservice.ex @@ -0,0 +1,27 @@ +defmodule Explorer.Utility.Microservice do + @moduledoc """ + Module is responsible for common utils related to microservices. + """ + def base_url(application \\ :explorer, module) do + url = Application.get_env(application, module)[:service_url] + + if String.ends_with?(url, "/") do + url + |> String.slice(0..(String.length(url) - 2)) + else + url + end + end + + @doc """ + Returns :ok if Application.get_env(:explorer, module)[:enabled] is true (module is enabled) + """ + @spec check_enabled(atom) :: :ok | {:error, :disabled} + def check_enabled(module) do + if Application.get_env(:explorer, module)[:enabled] do + :ok + else + {:error, :disabled} + end + end +end diff --git a/apps/explorer/lib/explorer/utility/rust_service.ex b/apps/explorer/lib/explorer/utility/rust_service.ex deleted file mode 100644 index 63f949961252..000000000000 --- a/apps/explorer/lib/explorer/utility/rust_service.ex +++ /dev/null @@ -1,15 +0,0 @@ -defmodule Explorer.Utility.RustService do - @moduledoc """ - Module is responsible for common utils related to rust microservices. - """ - def base_url(module) do - url = Application.get_env(:explorer, module)[:service_url] - - if String.ends_with?(url, "/") do - url - |> String.slice(0..(String.length(url) - 2)) - else - url - end - end -end diff --git a/apps/explorer/lib/explorer/utility/token_transfer_token_id_migrator_progress.ex b/apps/explorer/lib/explorer/utility/token_transfer_token_id_migrator_progress.ex deleted file mode 100644 index 3a28181016e5..000000000000 --- a/apps/explorer/lib/explorer/utility/token_transfer_token_id_migrator_progress.ex +++ /dev/null @@ -1,67 +0,0 @@ -defmodule Explorer.Utility.TokenTransferTokenIdMigratorProgress do - @moduledoc """ - Module is responsible for keeping the current progress of TokenTransfer token_id migration. - Full algorithm is in the 'Indexer.Fetcher.TokenTransferTokenIdMigration.Supervisor' module doc. - """ - use Explorer.Schema - - require Logger - - alias Explorer.Chain.Cache.BlockNumber - alias Explorer.Repo - - schema "token_transfer_token_id_migrator_progress" do - field(:last_processed_block_number, :integer) - - timestamps() - end - - @doc false - def changeset(progress \\ %__MODULE__{}, params) do - cast(progress, params, [:last_processed_block_number]) - end - - def get_current_progress do - Repo.one( - from( - p in __MODULE__, - order_by: [desc: p.updated_at], - limit: 1 - ) - ) - end - - def get_last_processed_block_number do - case get_current_progress() do - nil -> - latest_processed_block_number = BlockNumber.get_max() + 1 - update_last_processed_block_number(latest_processed_block_number) - latest_processed_block_number - - %{last_processed_block_number: block_number} -> - block_number - end - end - - def update_last_processed_block_number(block_number, force \\ false) do - case get_current_progress() do - nil -> - %{last_processed_block_number: block_number} - |> changeset() - |> Repo.insert() - - progress -> - if not force and progress.last_processed_block_number < block_number do - Logger.error( - "TokenTransferTokenIdMigratorProgress new block_number is above the last one. Last: #{progress.last_processed_block_number}, new: #{block_number}" - ) - - {:error, :invalid_block_number} - else - progress - |> changeset(%{last_processed_block_number: block_number}) - |> Repo.update() - end - end - end -end diff --git a/apps/explorer/lib/explorer/visualize/sol2uml.ex b/apps/explorer/lib/explorer/visualize/sol2uml.ex index 10a6c2a9efda..459531a36299 100644 --- a/apps/explorer/lib/explorer/visualize/sol2uml.ex +++ b/apps/explorer/lib/explorer/visualize/sol2uml.ex @@ -2,7 +2,7 @@ defmodule Explorer.Visualize.Sol2uml do @moduledoc """ Adapter for sol2uml visualizer with https://github.com/blockscout/blockscout-rs/blob/main/visualizer """ - alias Explorer.Utility.RustService + alias Explorer.Utility.Microservice alias HTTPoison.Response require Logger @@ -61,7 +61,7 @@ defmodule Explorer.Visualize.Sol2uml do def base_api_url, do: "#{base_url()}" <> "/api/v1" def base_url do - RustService.base_url(__MODULE__) + Microservice.base_url(__MODULE__) end def enabled?, do: Application.get_env(:explorer, __MODULE__)[:enabled] diff --git a/apps/explorer/mix.exs b/apps/explorer/mix.exs index fd91cf515e59..ae066dfdc75f 100644 --- a/apps/explorer/mix.exs +++ b/apps/explorer/mix.exs @@ -24,7 +24,7 @@ defmodule Explorer.Mixfile do dialyzer: :test ], start_permanent: Mix.env() == :prod, - version: "5.3.2", + version: "6.0.0", xref: [exclude: [BlockScoutWeb.WebRouter.Helpers]] ] end @@ -61,7 +61,7 @@ defmodule Explorer.Mixfile do {:mime, "~> 2.0"}, {:bcrypt_elixir, "~> 3.0"}, # benchmark optimizations - {:benchee, "~> 1.2.0", only: :test}, + {:benchee, "~> 1.3.0", only: :test}, # CSV output for benchee {:benchee_csv, "~> 1.0.0", only: :test}, {:bypass, "~> 2.1", only: :test}, diff --git a/apps/explorer/package-lock.json b/apps/explorer/package-lock.json index 86af32e1175b..88f5dd663846 100644 --- a/apps/explorer/package-lock.json +++ b/apps/explorer/package-lock.json @@ -28,9 +28,9 @@ } }, "node_modules/follow-redirects": { - "version": "1.14.8", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.8.tgz", - "integrity": "sha512-1x0S9UVJHsQprFcEC/qnNzBLcIxsjAV905f/UkQxbclCsoTWlacCNOpQa/anodLl2uaEKFhfWOvM2Qg77+15zA==", + "version": "1.15.4", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.4.tgz", + "integrity": "sha512-Cr4D/5wlrb0z9dgERpUL3LrmPKVDsETIJhaCMeDfuFYcqa5bldGV6wBsAN6X/vxlXQtFBMrXdXxdL8CbDTGniw==", "funding": [ { "type": "individual", @@ -134,9 +134,9 @@ "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==" }, "follow-redirects": { - "version": "1.14.8", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.8.tgz", - "integrity": "sha512-1x0S9UVJHsQprFcEC/qnNzBLcIxsjAV905f/UkQxbclCsoTWlacCNOpQa/anodLl2uaEKFhfWOvM2Qg77+15zA==" + "version": "1.15.4", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.4.tgz", + "integrity": "sha512-Cr4D/5wlrb0z9dgERpUL3LrmPKVDsETIJhaCMeDfuFYcqa5bldGV6wBsAN6X/vxlXQtFBMrXdXxdL8CbDTGniw==" }, "js-sha3": { "version": "0.8.0", diff --git a/apps/explorer/priv/account/migrations/20231207201701_add_watchlist_id_column.exs b/apps/explorer/priv/account/migrations/20231207201701_add_watchlist_id_column.exs new file mode 100644 index 000000000000..346c9ec05ee8 --- /dev/null +++ b/apps/explorer/priv/account/migrations/20231207201701_add_watchlist_id_column.exs @@ -0,0 +1,23 @@ +defmodule Explorer.Repo.Account.Migrations.AddWatchlistIdColumn do + use Ecto.Migration + + def change do + execute(""" + ALTER TABLE public.account_watchlist_notifications + DROP CONSTRAINT account_watchlist_notifications_watchlist_address_id_fkey; + """) + + alter table(:account_watchlist_notifications) do + add(:watchlist_id, :bigserial) + end + + create(index(:account_watchlist_notifications, [:watchlist_id])) + + execute(""" + UPDATE account_watchlist_notifications awn + SET watchlist_id = awa.watchlist_id + FROM account_watchlist_addresses awa + WHERE awa.id = awn.watchlist_address_id + """) + end +end diff --git a/apps/explorer/priv/repo/migrations/20231212101547_add_block_timestamp_and_consensus_to_transactions.exs b/apps/explorer/priv/repo/migrations/20231212101547_add_block_timestamp_and_consensus_to_transactions.exs new file mode 100644 index 000000000000..458e29604383 --- /dev/null +++ b/apps/explorer/priv/repo/migrations/20231212101547_add_block_timestamp_and_consensus_to_transactions.exs @@ -0,0 +1,13 @@ +defmodule Explorer.Repo.Migrations.AddBlockTimestampAndConsensusToTransactions do + use Ecto.Migration + + def change do + alter table(:transactions) do + add_if_not_exists(:block_timestamp, :utc_datetime_usec) + add_if_not_exists(:block_consensus, :boolean, default: true) + end + + create_if_not_exists(index(:transactions, :block_timestamp)) + create_if_not_exists(index(:transactions, :block_consensus)) + end +end diff --git a/apps/explorer/priv/repo/migrations/20231212102127_create_migrations_status.exs b/apps/explorer/priv/repo/migrations/20231212102127_create_migrations_status.exs new file mode 100644 index 000000000000..0b8bf54a5c3b --- /dev/null +++ b/apps/explorer/priv/repo/migrations/20231212102127_create_migrations_status.exs @@ -0,0 +1,12 @@ +defmodule Explorer.Repo.Migrations.CreateMigrationsStatus do + use Ecto.Migration + + def change do + create table(:migrations_status, primary_key: false) do + add(:migration_name, :string, primary_key: true) + add(:status, :string) + + timestamps() + end + end +end diff --git a/apps/explorer/priv/repo/migrations/20231213085254_add_btree_gin_extension.exs b/apps/explorer/priv/repo/migrations/20231213085254_add_btree_gin_extension.exs new file mode 100644 index 000000000000..b34f9ee059cb --- /dev/null +++ b/apps/explorer/priv/repo/migrations/20231213085254_add_btree_gin_extension.exs @@ -0,0 +1,11 @@ +defmodule Explorer.Repo.Migrations.CreateBtreeGinExtension do + use Ecto.Migration + + def up do + execute("CREATE EXTENSION IF NOT EXISTS btree_gin") + end + + def down do + execute("DROP EXTENSION IF EXISTS btree_gin") + end +end diff --git a/apps/explorer/priv/repo/migrations/20231213090140_add_token_transfers_token_contract_address_token_ids_index.exs b/apps/explorer/priv/repo/migrations/20231213090140_add_token_transfers_token_contract_address_token_ids_index.exs new file mode 100644 index 000000000000..7636fc7b3c2f --- /dev/null +++ b/apps/explorer/priv/repo/migrations/20231213090140_add_token_transfers_token_contract_address_token_ids_index.exs @@ -0,0 +1,26 @@ +defmodule Explorer.Repo.Migrations.AddTokenTransfersTokenContractAddressTokenIdsIndex do + use Ecto.Migration + @disable_ddl_transaction true + @disable_migration_lock true + + def up do + create( + index( + :token_transfers, + [:token_contract_address_hash, :token_ids], + name: "token_transfers_token_contract_address_hash_token_ids_index", + using: "GIN", + concurrently: true + ) + ) + end + + def down do + drop_if_exists( + index(:token_transfers, [:token_contract_address_hash, :token_ids], + name: :token_transfers_token_contract_address_hash_token_ids_index + ), + concurrently: true + ) + end +end diff --git a/apps/explorer/priv/repo/migrations/20231213101235_drop_token_transfers_token_ids_index.exs b/apps/explorer/priv/repo/migrations/20231213101235_drop_token_transfers_token_ids_index.exs new file mode 100644 index 000000000000..0065d2e26312 --- /dev/null +++ b/apps/explorer/priv/repo/migrations/20231213101235_drop_token_transfers_token_ids_index.exs @@ -0,0 +1,7 @@ +defmodule Explorer.Repo.Migrations.DropTokenTransfersTokenIdsIndex do + use Ecto.Migration + + def change do + drop_if_exists(index(:token_transfers, [:token_ids], name: :token_transfers_token_ids_index)) + end +end diff --git a/apps/explorer/priv/repo/migrations/20231213152332_alter_log_topic_columns_type.exs b/apps/explorer/priv/repo/migrations/20231213152332_alter_log_topic_columns_type.exs new file mode 100644 index 000000000000..649c95d0dbf1 --- /dev/null +++ b/apps/explorer/priv/repo/migrations/20231213152332_alter_log_topic_columns_type.exs @@ -0,0 +1,18 @@ +defmodule Explorer.Repo.Migrations.AlterLogTopicColumnsType do + use Ecto.Migration + + def change do + execute(""" + ALTER TABLE logs + ALTER COLUMN first_topic TYPE bytea + USING CAST(REPLACE(first_topic, '0x', '\\x') as bytea), + ALTER COLUMN second_topic TYPE bytea + USING CAST(REPLACE(second_topic, '0x', '\\x') as bytea), + ALTER COLUMN third_topic TYPE bytea + USING CAST(REPLACE(third_topic, '0x', '\\x') as bytea), + ALTER COLUMN fourth_topic TYPE bytea + USING CAST(REPLACE(fourth_topic, '0x', '\\x') as bytea) + ; + """) + end +end diff --git a/apps/explorer/priv/repo/migrations/20231215094615_drop_token_transfers_token_id_column.exs b/apps/explorer/priv/repo/migrations/20231215094615_drop_token_transfers_token_id_column.exs new file mode 100644 index 000000000000..df8637071b31 --- /dev/null +++ b/apps/explorer/priv/repo/migrations/20231215094615_drop_token_transfers_token_id_column.exs @@ -0,0 +1,12 @@ +defmodule Explorer.Repo.Migrations.DropTokenTransfersTokenIdColumn do + use Ecto.Migration + + def change do + drop(index(:token_transfers, [:token_id])) + drop(index(:token_transfers, [:token_contract_address_hash, "token_id DESC", "block_number DESC"])) + + alter table(:token_transfers) do + remove(:token_id) + end + end +end diff --git a/apps/explorer/priv/repo/migrations/20231215104320_drop_unused_actb_indexes.exs b/apps/explorer/priv/repo/migrations/20231215104320_drop_unused_actb_indexes.exs new file mode 100644 index 000000000000..0f8ad39f3bc0 --- /dev/null +++ b/apps/explorer/priv/repo/migrations/20231215104320_drop_unused_actb_indexes.exs @@ -0,0 +1,21 @@ +defmodule Explorer.Repo.Migrations.DropUnusedActbIndexes do + use Ecto.Migration + + def change do + drop( + index( + :address_current_token_balances, + [:value], + name: :address_current_token_balances_value, + where: "value IS NOT NULL" + ) + ) + + drop( + index( + :address_current_token_balances, + [:address_hash, :block_number, :token_contract_address_hash] + ) + ) + end +end diff --git a/apps/explorer/priv/repo/migrations/20231215115638_drop_unused_logs_type_index.exs b/apps/explorer/priv/repo/migrations/20231215115638_drop_unused_logs_type_index.exs new file mode 100644 index 000000000000..fc7df4ddce8c --- /dev/null +++ b/apps/explorer/priv/repo/migrations/20231215115638_drop_unused_logs_type_index.exs @@ -0,0 +1,11 @@ +defmodule Explorer.Repo.Migrations.DropUnusedLogsTypeIndex do + use Ecto.Migration + + def change do + drop(index(:logs, [:type], name: :logs_type_index)) + + alter table(:logs) do + remove(:type) + end + end +end diff --git a/apps/explorer/priv/repo/migrations/20231215132609_add_index_blocks_refetch_needed.exs b/apps/explorer/priv/repo/migrations/20231215132609_add_index_blocks_refetch_needed.exs new file mode 100644 index 000000000000..aef72e5ed81c --- /dev/null +++ b/apps/explorer/priv/repo/migrations/20231215132609_add_index_blocks_refetch_needed.exs @@ -0,0 +1,15 @@ +defmodule Explorer.Repo.Migrations.AddIndexBlocksRefetchNeeded do + use Ecto.Migration + + def up do + execute(""" + CREATE INDEX consensus_block_hashes_refetch_needed ON blocks(hash) WHERE consensus and refetch_needed; + """) + end + + def down do + execute(""" + DROP INDEX consensus_block_hashes_refetch_needed; + """) + end +end diff --git a/apps/explorer/priv/repo/migrations/20231225113850_transactions_asc_indices.exs b/apps/explorer/priv/repo/migrations/20231225113850_transactions_asc_indices.exs new file mode 100644 index 000000000000..b0f668e89cd5 --- /dev/null +++ b/apps/explorer/priv/repo/migrations/20231225113850_transactions_asc_indices.exs @@ -0,0 +1,47 @@ +defmodule Explorer.Repo.Migrations.TransactionsAscIndices do + use Ecto.Migration + + def change do + create( + index( + :transactions, + [ + :from_address_hash, + "block_number ASC NULLS LAST", + "index ASC NULLS LAST", + "inserted_at ASC", + "hash DESC" + ], + name: "transactions_from_address_hash_with_pending_index_asc" + ) + ) + + create( + index( + :transactions, + [ + :to_address_hash, + "block_number ASC NULLS LAST", + "index ASC NULLS LAST", + "inserted_at ASC", + "hash DESC" + ], + name: "transactions_to_address_hash_with_pending_index_asc" + ) + ) + + create( + index( + :transactions, + [ + :created_contract_address_hash, + "block_number ASC NULLS LAST", + "index ASC NULLS LAST", + "inserted_at ASC", + "hash DESC" + ], + name: "transactions_created_contract_address_hash_with_pending_index_a" + ) + ) + end +end diff --git a/apps/explorer/priv/repo/migrations/20231225115026_logs_asc_index.exs b/apps/explorer/priv/repo/migrations/20231225115026_logs_asc_index.exs new file mode 100644 index 000000000000..7e7fc0963d6a --- /dev/null +++ b/apps/explorer/priv/repo/migrations/20231225115026_logs_asc_index.exs @@ -0,0 +1,7 @@ +defmodule Explorer.Repo.Migrations.LogsAscIndex do + use Ecto.Migration + + def change do + create(index(:logs, ["block_number ASC, index ASC"])) + end +end diff --git a/apps/explorer/priv/repo/migrations/20231225115100_token_transfers_asc_index.exs b/apps/explorer/priv/repo/migrations/20231225115100_token_transfers_asc_index.exs new file mode 100644 index 000000000000..084ce83adb35 --- /dev/null +++ b/apps/explorer/priv/repo/migrations/20231225115100_token_transfers_asc_index.exs @@ -0,0 +1,7 @@ +defmodule Explorer.Repo.Migrations.TokenTransfersAscIndex do + use Ecto.Migration + + def change do + create(index(:token_transfers, ["block_number ASC", "log_index ASC"])) + end +end diff --git a/apps/explorer/priv/repo/migrations/20240103094720_constrain_null_date_market_history.exs b/apps/explorer/priv/repo/migrations/20240103094720_constrain_null_date_market_history.exs new file mode 100644 index 000000000000..ab43538e4e1e --- /dev/null +++ b/apps/explorer/priv/repo/migrations/20240103094720_constrain_null_date_market_history.exs @@ -0,0 +1,9 @@ +defmodule Explorer.Repo.Migrations.ConstrainNullDateMarketHistory do + use Ecto.Migration + + def change do + alter table(:market_history) do + modify(:date, :date, null: false, from: {:date, null: true}) + end + end +end diff --git a/apps/explorer/test/explorer/account/notifier/notify_test.exs b/apps/explorer/test/explorer/account/notifier/notify_test.exs index bc7480cc3ec2..860b569f2a15 100644 --- a/apps/explorer/test/explorer/account/notifier/notify_test.exs +++ b/apps/explorer/test/explorer/account/notifier/notify_test.exs @@ -86,5 +86,61 @@ defmodule Explorer.Account.Notifier.NotifyTest do assert wn.tx_fee == fee assert wn.type == "COIN" end + + test "ignore new notification when limit is reached" do + old_envs = Application.get_env(:explorer, Explorer.Account) + + Application.put_env(:explorer, Explorer.Account, Keyword.put(old_envs, :notifications_limit_for_30_days, 1)) + + wa = + %WatchlistAddress{address_hash: address_hash} = + build(:account_watchlist_address, watch_coin_input: true) + |> Repo.account_repo().insert!() + + _watchlist_address = Repo.preload(wa, watchlist: :identity) + + tx = + %Transaction{ + from_address: _from_address, + to_address: _to_address, + block_number: _block_number, + hash: _tx_hash + } = with_block(insert(:transaction, to_address: %Chain.Address{hash: address_hash})) + + {_, fee} = Chain.fee(tx, :gwei) + amount = Wei.to(tx.value, :ether) + notify = Notify.call([tx]) + + wn = + WatchlistNotification + |> first + |> Repo.account_repo().one() + + assert notify == [[:ok]] + + assert wn.amount == amount + assert wn.direction == "incoming" + assert wn.method == "transfer" + assert wn.subject == "Coin transaction" + assert wn.tx_fee == fee + assert wn.type == "COIN" + address = Repo.get(Chain.Address, address_hash) + + tx = + %Transaction{ + from_address: _from_address, + to_address: _to_address, + block_number: _block_number, + hash: _tx_hash + } = with_block(insert(:transaction, to_address: address)) + + Notify.call([tx]) + + WatchlistNotification + |> first + |> Repo.account_repo().one!() + + Application.put_env(:explorer, Explorer.Account, old_envs) + end end end diff --git a/apps/explorer/test/explorer/chain/address/token_balance_test.exs b/apps/explorer/test/explorer/chain/address/token_balance_test.exs index 9a72837f6a5f..3e1a8018289a 100644 --- a/apps/explorer/test/explorer/chain/address/token_balance_test.exs +++ b/apps/explorer/test/explorer/chain/address/token_balance_test.exs @@ -46,6 +46,7 @@ defmodule Explorer.Chain.Address.TokenBalanceTest do :token_balance, address: burn_address, token_contract_address_hash: token.contract_address_hash, + token_type: "ERC-721", value_fetched_at: nil ) diff --git a/apps/explorer/test/explorer/chain/block_test.exs b/apps/explorer/test/explorer/chain/block_test.exs index 35f14c54795c..703cd8609a7b 100644 --- a/apps/explorer/test/explorer/chain/block_test.exs +++ b/apps/explorer/test/explorer/chain/block_test.exs @@ -2,7 +2,7 @@ defmodule Explorer.Chain.BlockTest do use Explorer.DataCase alias Ecto.Changeset - alias Explorer.Chain.Block + alias Explorer.Chain.{Block, Wei} describe "changeset/2" do test "with valid attributes" do @@ -58,4 +58,46 @@ defmodule Explorer.Chain.BlockTest do assert Enum.member?(results, unrewarded_block.hash) end end + + describe "block_reward_by_parts/1" do + setup do + {:ok, emission_reward: insert(:emission_reward)} + end + + test "without uncles", %{emission_reward: %{reward: reward, block_range: range}} do + block = build(:block, number: range.from, base_fee_per_gas: 5, uncles: []) + + tx1 = build(:transaction, gas_price: 1, gas_used: 1, block_number: block.number, block_hash: block.hash) + tx2 = build(:transaction, gas_price: 1, gas_used: 2, block_number: block.number, block_hash: block.hash) + + tx3 = + build(:transaction, + gas_price: 1, + gas_used: 3, + block_number: block.number, + block_hash: block.hash, + max_priority_fee_per_gas: 1 + ) + + expected_transaction_fees = %Wei{value: Decimal.new(6)} + expected_burnt_fees = %Wei{value: Decimal.new(30)} + expected_uncle_reward = %Wei{value: Decimal.new(0)} + + assert %{ + static_reward: ^reward, + transaction_fees: ^expected_transaction_fees, + burnt_fees: ^expected_burnt_fees, + uncle_reward: ^expected_uncle_reward + } = Block.block_reward_by_parts(block, [tx1, tx2, tx3]) + end + + test "with uncles", %{emission_reward: %{reward: reward, block_range: range}} do + block = + build(:block, number: range.from, uncles: ["0xe670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d15273311"]) + + expected_uncle_reward = Wei.mult(reward, Decimal.from_float(1 / 32)) + + assert %{uncle_reward: ^expected_uncle_reward} = Block.block_reward_by_parts(block, []) + end + end end diff --git a/apps/explorer/test/explorer/chain/cache/gas_price_oracle_test.exs b/apps/explorer/test/explorer/chain/cache/gas_price_oracle_test.exs index d7004d11d0ed..a30d4070f66f 100644 --- a/apps/explorer/test/explorer/chain/cache/gas_price_oracle_test.exs +++ b/apps/explorer/test/explorer/chain/cache/gas_price_oracle_test.exs @@ -4,6 +4,7 @@ defmodule Explorer.Chain.Cache.GasPriceOracleTest do import Mox alias Explorer.Chain.Cache.GasPriceOracle + alias Explorer.Counters.AverageBlockTime @block %{ "difficulty" => "0x0", @@ -48,12 +49,12 @@ defmodule Explorer.Chain.Cache.GasPriceOracleTest do test "returns nil percentile values if no blocks in the DB" do expect(EthereumJSONRPC.Mox, :json_rpc, fn [%{id: id}], _options -> {:ok, [%{id: id, result: @block}]} end) - assert {:ok, - %{ - "slow" => nil, - "average" => nil, - "fast" => nil - }} = GasPriceOracle.get_average_gas_price(3, 35, 60, 90) + assert {{:ok, + %{ + slow: nil, + average: nil, + fast: nil + }}, []} = GasPriceOracle.get_average_gas_price(3, 35, 60, 90) end test "returns nil percentile values if blocks are empty in the DB" do @@ -63,12 +64,12 @@ defmodule Explorer.Chain.Cache.GasPriceOracleTest do insert(:block) insert(:block) - assert {:ok, - %{ - "slow" => nil, - "average" => nil, - "fast" => nil - }} = GasPriceOracle.get_average_gas_price(3, 35, 60, 90) + assert {{:ok, + %{ + slow: nil, + average: nil, + fast: nil + }}, []} = GasPriceOracle.get_average_gas_price(3, 35, 60, 90) end test "returns nil percentile values for blocks with failed txs in the DB" do @@ -89,12 +90,12 @@ defmodule Explorer.Chain.Cache.GasPriceOracleTest do hash: "0xac2a7dab94d965893199e7ee01649e2d66f0787a4c558b3118c09e80d4df8269" ) - assert {:ok, - %{ - "slow" => nil, - "average" => nil, - "fast" => nil - }} = GasPriceOracle.get_average_gas_price(3, 35, 60, 90) + assert {{:ok, + %{ + slow: nil, + average: nil, + fast: nil + }}, []} = GasPriceOracle.get_average_gas_price(3, 35, 60, 90) end test "returns nil percentile values for transactions with 0 gas price aka 'whitelisted transactions' in the DB" do @@ -127,12 +128,12 @@ defmodule Explorer.Chain.Cache.GasPriceOracleTest do hash: "0x5d5c2776f96704e7845f7d3c1fbba6685ab6efd6f82b6cd11d549f3b3a46bd03" ) - assert {:ok, - %{ - "slow" => nil, - "average" => nil, - "fast" => nil - }} = GasPriceOracle.get_average_gas_price(2, 35, 60, 90) + assert {{:ok, + %{ + slow: nil, + average: nil, + fast: nil + }}, []} = GasPriceOracle.get_average_gas_price(2, 35, 60, 90) end test "returns the same percentile values if gas price is the same over transactions" do @@ -165,12 +166,12 @@ defmodule Explorer.Chain.Cache.GasPriceOracleTest do hash: "0x5d5c2776f96704e7845f7d3c1fbba6685ab6efd6f82b6cd11d549f3b3a46bd03" ) - assert {:ok, - %{ - "slow" => 1.0, - "average" => 1.0, - "fast" => 1.0 - }} = GasPriceOracle.get_average_gas_price(2, 35, 60, 90) + assert {{:ok, + %{ + slow: %{price: 1.0}, + average: %{price: 1.0}, + fast: %{price: 1.0} + }}, _} = GasPriceOracle.get_average_gas_price(2, 35, 60, 90) end test "returns correct min gas price from the block" do @@ -215,12 +216,12 @@ defmodule Explorer.Chain.Cache.GasPriceOracleTest do hash: "0x906b80861b4a0921acfbb91a7b527227b0d32adabc88bc73e8c52ff714e55016" ) - assert {:ok, - %{ - "slow" => 1.0, - "average" => 2.0, - "fast" => 2.0 - }} = GasPriceOracle.get_average_gas_price(3, 35, 60, 90) + assert {{:ok, + %{ + slow: %{price: 1.0}, + average: %{price: 2.0}, + fast: %{price: 2.0} + }}, _} = GasPriceOracle.get_average_gas_price(3, 35, 60, 90) end test "returns correct average percentile" do @@ -266,10 +267,10 @@ defmodule Explorer.Chain.Cache.GasPriceOracleTest do hash: "0x7d4bc5569053fc29f471901e967c9e60205ac7a122b0e9a789683652c34cc11a" ) - assert {:ok, - %{ - "average" => 3.34 - }} = GasPriceOracle.get_average_gas_price(3, 35, 60, 90) + assert {{:ok, + %{ + average: %{price: 3.34} + }}, _} = GasPriceOracle.get_average_gas_price(3, 35, 60, 90) end test "returns correct gas price for EIP-1559 transactions" do @@ -320,13 +321,176 @@ defmodule Explorer.Chain.Cache.GasPriceOracleTest do hash: "0x906b80861b4a0921acfbb91a7b527227b0d32adabc88bc73e8c52ff714e55016" ) - assert {:ok, - %{ - # including base fee - "slow" => 1.5, - "average" => 2.5, - "fast" => 2.5 - }} = GasPriceOracle.get_average_gas_price(3, 35, 60, 90) + assert {{:ok, + %{ + # including base fee + slow: %{price: 1.5}, + average: %{price: 2.5} + }}, _} = GasPriceOracle.get_average_gas_price(3, 35, 60, 90) + end + + test "return gas prices with time if available" do + expect(EthereumJSONRPC.Mox, :json_rpc, fn [%{id: id}], _options -> {:ok, [%{id: id, result: @block}]} end) + + block1 = + insert(:block, + number: 100, + hash: "0x3e51328bccedee581e8ba35190216a61a5d67fd91ca528f3553142c0c7d18391", + timestamp: ~U[2023-12-12 12:12:30.000000Z] + ) + + block2 = + insert(:block, + number: 101, + hash: "0x76c3da57334fffdc66c0d954dce1a910fcff13ec889a13b2d8b0b6e9440ce729", + timestamp: ~U[2023-12-12 12:13:00.000000Z] + ) + + :transaction + |> insert( + status: :ok, + block_hash: block1.hash, + block_number: block1.number, + block_timestamp: block1.timestamp, + cumulative_gas_used: 884_322, + gas_used: 106_025, + index: 0, + gas_price: 1_000_000_000, + max_priority_fee_per_gas: 1_000_000_000, + max_fee_per_gas: 1_000_000_000, + hash: "0xac2a7dab94d965893199e7ee01649e2d66f0787a4c558b3118c09e80d4df8269", + earliest_processing_start: ~U[2023-12-12 12:12:00.000000Z] + ) + + :transaction + |> insert( + status: :ok, + block_hash: block2.hash, + block_number: block2.number, + block_timestamp: block2.timestamp, + cumulative_gas_used: 884_322, + gas_used: 106_025, + index: 0, + gas_price: 1_000_000_000, + max_priority_fee_per_gas: 1_000_000_000, + max_fee_per_gas: 1_000_000_000, + hash: "0x5d5c2776f96704e7845f7d3c1fbba6685ab6efd6f82b6cd11d549f3b3a46bd03", + earliest_processing_start: ~U[2023-12-12 12:12:00.000000Z] + ) + + :transaction + |> insert( + status: :ok, + block_hash: block2.hash, + block_number: block2.number, + block_timestamp: block2.timestamp, + cumulative_gas_used: 884_322, + gas_used: 106_025, + index: 1, + gas_price: 3_000_000_000, + max_priority_fee_per_gas: 3_000_000_000, + max_fee_per_gas: 3_000_000_000, + hash: "0x906b80861b4a0921acfbb91a7b527227b0d32adabc88bc73e8c52ff714e55016", + earliest_processing_start: ~U[2023-12-12 12:12:55.000000Z] + ) + + assert {{ + :ok, + %{ + average: %{price: 2.5, time: 15000.0}, + fast: %{price: 2.5, time: 15000.0}, + slow: %{price: 1.5, time: 17500.0} + } + }, _} = GasPriceOracle.get_average_gas_price(3, 35, 60, 90) + end + + test "return gas prices with average block time if earliest_processing_start is not available" do + expect(EthereumJSONRPC.Mox, :json_rpc, fn [%{id: id}], _options -> {:ok, [%{id: id, result: @block}]} end) + old_env = Application.get_env(:explorer, AverageBlockTime) + Application.put_env(:explorer, AverageBlockTime, enabled: true, cache_period: 1_800_000) + start_supervised!(AverageBlockTime) + + block_number = 99_999_999 + first_timestamp = ~U[2023-12-12 12:12:30.000000Z] + + Enum.each(1..100, fn i -> + insert(:block, + number: block_number + 1 + i, + consensus: true, + timestamp: Timex.shift(first_timestamp, seconds: -(101 - i) - 12) + ) + end) + + block1 = + insert(:block, + number: block_number + 102, + consensus: true, + timestamp: Timex.shift(first_timestamp, seconds: -10) + ) + + block2 = + insert(:block, + number: block_number + 103, + consensus: true, + timestamp: Timex.shift(first_timestamp, seconds: -7) + ) + + AverageBlockTime.refresh() + + :transaction + |> insert( + status: :ok, + block_hash: block1.hash, + block_number: block1.number, + cumulative_gas_used: 884_322, + gas_used: 106_025, + index: 0, + gas_price: 1_000_000_000, + max_priority_fee_per_gas: 1_000_000_000, + max_fee_per_gas: 1_000_000_000, + hash: "0xac2a7dab94d965893199e7ee01649e2d66f0787a4c558b3118c09e80d4df8269" + ) + + :transaction + |> insert( + status: :ok, + block_hash: block2.hash, + block_number: block2.number, + cumulative_gas_used: 884_322, + gas_used: 106_025, + index: 0, + gas_price: 1_000_000_000, + max_priority_fee_per_gas: 1_000_000_000, + max_fee_per_gas: 1_000_000_000, + hash: "0x5d5c2776f96704e7845f7d3c1fbba6685ab6efd6f82b6cd11d549f3b3a46bd03" + ) + + :transaction + |> insert( + status: :ok, + block_hash: block2.hash, + block_number: block2.number, + cumulative_gas_used: 884_322, + gas_used: 106_025, + index: 1, + gas_price: 3_000_000_000, + max_priority_fee_per_gas: 3_000_000_000, + max_fee_per_gas: 3_000_000_000, + hash: "0x906b80861b4a0921acfbb91a7b527227b0d32adabc88bc73e8c52ff714e55016" + ) + + AverageBlockTime.refresh() + + assert {{ + :ok, + %{ + average: %{price: 2.5, time: 1000.0}, + fast: %{price: 2.5, time: 1000.0}, + slow: %{price: 1.5, time: 1000.0} + } + }, _} = GasPriceOracle.get_average_gas_price(3, 35, 60, 90) + + Application.put_env(:explorer, AverageBlockTime, old_env) end end end diff --git a/apps/explorer/test/explorer/chain/csv_export/address_log_csv_exporter_test.exs b/apps/explorer/test/explorer/chain/csv_export/address_log_csv_exporter_test.exs index bff1ca818d33..e70b2eb9cb25 100644 --- a/apps/explorer/test/explorer/chain/csv_export/address_log_csv_exporter_test.exs +++ b/apps/explorer/test/explorer/chain/csv_export/address_log_csv_exporter_test.exs @@ -4,6 +4,16 @@ defmodule Explorer.Chain.AddressLogCsvExporterTest do alias Explorer.Chain.Address alias Explorer.Chain.CSVExport.AddressLogCsvExporter + @first_topic_hex_string_1 "0x7fcf532c15f0a6db0bd6d0e038bea71d30d808c7d98cb3bf7268a95bf5081b65" + @second_topic_hex_string_1 "0x00000000000000000000000098a9dc37d3650b5b30d6c12789b3881ee0b70c16" + @third_topic_hex_string_1 "0x0000000000000000000000005079fc00f00f30000e0c8c083801cfde000008b6" + @fourth_topic_hex_string_1 "0x8c9b7729443a4444242342b2ca385a239a5c1d76a88473e1cd2ab0c70dd1b9c7" + + defp topic(topic_hex_string) do + {:ok, topic} = Explorer.Chain.Hash.Full.cast(topic_hex_string) + topic + end + describe "export/3" do test "exports address logs to csv" do address = insert(:address) @@ -21,10 +31,10 @@ defmodule Explorer.Chain.AddressLogCsvExporterTest do block: transaction.block, block_number: transaction.block_number, data: "0x12", - first_topic: "0x13", - second_topic: "0x14", - third_topic: "0x15", - fourth_topic: "0x16" + first_topic: topic(@first_topic_hex_string_1), + second_topic: topic(@second_topic_hex_string_1), + third_topic: topic(@third_topic_hex_string_1), + fourth_topic: topic(@fourth_topic_hex_string_1) ) from_period = Timex.format!(Timex.shift(Timex.now(), minutes: -1), "%Y-%m-%d", :strftime) diff --git a/apps/explorer/test/explorer/chain/csv_export/address_token_transfer_csv_exporter_test.exs b/apps/explorer/test/explorer/chain/csv_export/address_token_transfer_csv_exporter_test.exs index 93873e221a93..cbc0a794cad7 100644 --- a/apps/explorer/test/explorer/chain/csv_export/address_token_transfer_csv_exporter_test.exs +++ b/apps/explorer/test/explorer/chain/csv_export/address_token_transfer_csv_exporter_test.exs @@ -73,7 +73,7 @@ defmodule Explorer.Chain.AddressTokenTransferCsvExporterTest do assert result.tx_hash == to_string(transaction.hash) assert result.from_address == Address.checksum(token_transfer.from_address_hash) assert result.to_address == Address.checksum(token_transfer.to_address_hash) - assert result.timestamp == to_string(transaction.block.timestamp) + assert result.timestamp == to_string(transaction.block_timestamp) assert result.type == "OUT" end diff --git a/apps/explorer/test/explorer/chain/import/runner/internal_transactions_test.exs b/apps/explorer/test/explorer/chain/import/runner/internal_transactions_test.exs index ffe02c5dc7df..b27bed0ebfa6 100644 --- a/apps/explorer/test/explorer/chain/import/runner/internal_transactions_test.exs +++ b/apps/explorer/test/explorer/chain/import/runner/internal_transactions_test.exs @@ -306,6 +306,29 @@ defmodule Explorer.Chain.Import.Runner.InternalTransactionsTest do assert PendingBlockOperation |> Repo.get(full_block.hash) |> is_nil() end + test "does not remove consensus from non-traceable blocks" do + original_config = Application.get_env(:indexer, :trace_block_ranges) + + full_block = insert(:block) + transaction_a = insert(:transaction) |> with_block(full_block) + transaction_b = insert(:transaction) |> with_block(full_block) + + Application.put_env(:indexer, :trace_block_ranges, "#{full_block.number + 1}..latest") + + insert(:pending_block_operation, block_hash: full_block.hash, block_number: full_block.number) + + transaction_a_changes = make_internal_transaction_changes(transaction_a, 0, nil) + + assert {:ok, _} = run_internal_transactions([transaction_a_changes]) + + assert from(i in InternalTransaction, where: i.transaction_hash == ^transaction_a.hash) |> Repo.one() |> is_nil() + assert from(i in InternalTransaction, where: i.transaction_hash == ^transaction_b.hash) |> Repo.one() |> is_nil() + + assert %{consensus: true} = Repo.get(Block, full_block.hash) + + on_exit(fn -> Application.put_env(:indexer, :trace_block_ranges, original_config) end) + end + test "successfully imports internal transaction with stop type" do block = insert(:block) transaction = insert(:transaction) |> with_block(block, status: :ok) diff --git a/apps/explorer/test/explorer/chain/import_test.exs b/apps/explorer/test/explorer/chain/import_test.exs index 322ca7a7ff80..0d2fbfa02de3 100644 --- a/apps/explorer/test/explorer/chain/import_test.exs +++ b/apps/explorer/test/explorer/chain/import_test.exs @@ -22,9 +22,16 @@ defmodule Explorer.Chain.ImportTest do @moduletag :capturelog + @first_topic_hex_string "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef" + @second_topic_hex_string "0x000000000000000000000000e8ddc5c7a2d2f0d7a9798459c0104fdf5e987aca" + @third_topic_hex_string "0x000000000000000000000000515c09c5bba1ed566b02a5b0599ec5d5d0aee73d" + doctest Import describe "all/1" do + {:ok, first_topic} = Explorer.Chain.Hash.Full.cast(@first_topic_hex_string) + {:ok, second_topic} = Explorer.Chain.Hash.Full.cast(@second_topic_hex_string) + {:ok, third_topic} = Explorer.Chain.Hash.Full.cast(@third_topic_hex_string) # set :timeout options to cover lines that use the timeout override when available @import_data %{ blocks: %{ @@ -91,13 +98,12 @@ defmodule Explorer.Chain.ImportTest do block_hash: "0xf6b4b8c88df3ebd252ec476328334dc026cf66606a84fb769b3d3cbccc8471bd", address_hash: "0x8bf38d4764929064f2d4d3a56520a76ab3df415b", data: "0x0000000000000000000000000000000000000000000000000de0b6b3a7640000", - first_topic: "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", - second_topic: "0x000000000000000000000000e8ddc5c7a2d2f0d7a9798459c0104fdf5e987aca", - third_topic: "0x000000000000000000000000515c09c5bba1ed566b02a5b0599ec5d5d0aee73d", + first_topic: first_topic, + second_topic: second_topic, + third_topic: third_topic, fourth_topic: nil, index: 0, - transaction_hash: "0x53bd884872de3e488692881baeec262e7b95234d3965248c39fe992fffd433e5", - type: "mined" + transaction_hash: "0x53bd884872de3e488692881baeec262e7b95234d3965248c39fe992fffd433e5" } ], timeout: 5 @@ -165,6 +171,9 @@ defmodule Explorer.Chain.ImportTest do } test "with valid data" do + {:ok, first_topic} = Explorer.Chain.Hash.Full.cast(@first_topic_hex_string) + {:ok, second_topic} = Explorer.Chain.Hash.Full.cast(@second_topic_hex_string) + {:ok, third_topic} = Explorer.Chain.Hash.Full.cast(@third_topic_hex_string) difficulty = Decimal.new(340_282_366_920_938_463_463_374_607_431_768_211_454) total_difficulty = Decimal.new(12_590_447_576_074_723_148_144_860_474_975_121_280_509) token_transfer_amount = Decimal.new(1_000_000_000_000_000_000) @@ -276,9 +285,9 @@ defmodule Explorer.Chain.ImportTest do 167, 100, 0, 0>> }, index: 0, - first_topic: "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", - second_topic: "0x000000000000000000000000e8ddc5c7a2d2f0d7a9798459c0104fdf5e987aca", - third_topic: "0x000000000000000000000000515c09c5bba1ed566b02a5b0599ec5d5d0aee73d", + first_topic: ^first_topic, + second_topic: ^second_topic, + third_topic: ^third_topic, fourth_topic: nil, transaction_hash: %Hash{ byte_count: 32, @@ -286,7 +295,6 @@ defmodule Explorer.Chain.ImportTest do <<83, 189, 136, 72, 114, 222, 62, 72, 134, 146, 136, 27, 174, 236, 38, 46, 123, 149, 35, 77, 57, 101, 36, 140, 57, 254, 153, 47, 255, 212, 51, 229>> }, - type: "mined", inserted_at: %{}, updated_at: %{} } diff --git a/apps/explorer/test/explorer/chain/log_test.exs b/apps/explorer/test/explorer/chain/log_test.exs index 6842a6b979fb..87881776de29 100644 --- a/apps/explorer/test/explorer/chain/log_test.exs +++ b/apps/explorer/test/explorer/chain/log_test.exs @@ -7,6 +7,13 @@ defmodule Explorer.Chain.LogTest do alias Explorer.Chain.{Log, SmartContract} alias Explorer.Repo + @first_topic_hex_string_1 "0x7fcf532c15f0a6db0bd6d0e038bea71d30d808c7d98cb3bf7268a95bf5081b65" + + defp topic(topic_hex_string) do + {:ok, topic} = Explorer.Chain.Hash.Full.cast(topic_hex_string) + topic + end + setup :set_mox_from_context doctest Log @@ -36,18 +43,21 @@ defmodule Explorer.Chain.LogTest do params_for( :log, address_hash: build(:address).hash, - first_topic: "ham", + first_topic: @first_topic_hex_string_1, transaction_hash: build(:transaction).hash, block_hash: build(:block).hash ) - assert %Changeset{changes: %{first_topic: "ham"}, valid?: true} = Log.changeset(%Log{}, params) + result = Log.changeset(%Log{}, params) + + assert result.valid? == true + assert result.changes.first_topic == topic(@first_topic_hex_string_1) end test "assigns optional attributes" do - params = Map.put(params_for(:log), :first_topic, "ham") + params = Map.put(params_for(:log), :first_topic, topic(@first_topic_hex_string_1)) changeset = Log.changeset(%Log{}, params) - assert changeset.changes.first_topic === "ham" + assert changeset.changes.first_topic === topic(@first_topic_hex_string_1) end end @@ -99,9 +109,9 @@ defmodule Explorer.Chain.LogTest do insert(:log, address: to_address, transaction: transaction, - first_topic: topic1, - second_topic: topic2, - third_topic: topic3, + first_topic: topic(topic1), + second_topic: topic(topic2), + third_topic: topic(topic3), fourth_topic: nil, data: data ) @@ -153,9 +163,9 @@ defmodule Explorer.Chain.LogTest do log = insert(:log, transaction: transaction, - first_topic: topic1, - second_topic: topic2, - third_topic: topic3, + first_topic: topic(topic1), + second_topic: topic(topic2), + third_topic: topic(topic3), fourth_topic: nil, data: data ) diff --git a/apps/explorer/test/explorer/chain/smart_contract/proxy_test.exs b/apps/explorer/test/explorer/chain/smart_contract/proxy_test.exs index d636e8967cd9..f5fd83b562a2 100644 --- a/apps/explorer/test/explorer/chain/smart_contract/proxy_test.exs +++ b/apps/explorer/test/explorer/chain/smart_contract/proxy_test.exs @@ -129,8 +129,7 @@ defmodule Explorer.Chain.SmartContract.ProxyTest do } ] - # EIP-1967 + EIP-1822 - defp request_zero_implementations do + defp request_EIP1967_zero_implementations do EthereumJSONRPC.Mox |> expect(:json_rpc, fn %{ id: 0, @@ -168,6 +167,11 @@ defmodule Explorer.Chain.SmartContract.ProxyTest do _options -> {:ok, "0x0000000000000000000000000000000000000000000000000000000000000000"} end) + end + + # EIP-1967 + EIP-1822 + defp request_zero_implementations do + request_EIP1967_zero_implementations() |> expect(:json_rpc, fn %{ id: 0, method: "eth_getStorageAt", @@ -206,6 +210,8 @@ defmodule Explorer.Chain.SmartContract.ProxyTest do smart_contract = insert(:smart_contract, address_hash: proxy_contract_address.hash, abi: @proxy_abi, contract_code_md5: "123") + request_zero_implementations() + assert Proxy.combine_proxy_implementation_abi(smart_contract) == @proxy_abi end @@ -226,6 +232,8 @@ defmodule Explorer.Chain.SmartContract.ProxyTest do implementation_contract_address_hash_string = Base.encode16(implementation_contract_address.hash.bytes, case: :lower) + request_zero_implementations() + expect( EthereumJSONRPC.Mox, :json_rpc, @@ -276,6 +284,8 @@ defmodule Explorer.Chain.SmartContract.ProxyTest do smart_contract = insert(:smart_contract, address_hash: proxy_contract_address.hash, abi: @proxy_abi, contract_code_md5: "123") + request_zero_implementations() + assert Proxy.get_implementation_abi_from_proxy(smart_contract, []) == [] end @@ -296,6 +306,8 @@ defmodule Explorer.Chain.SmartContract.ProxyTest do implementation_contract_address_hash_string = Base.encode16(implementation_contract_address.hash.bytes, case: :lower) + request_zero_implementations() + expect( EthereumJSONRPC.Mox, :json_rpc, @@ -316,7 +328,7 @@ defmodule Explorer.Chain.SmartContract.ProxyTest do assert implementation_abi == @implementation_abi end - test "get_implementation_abi_from_proxy/2 returns implementation abi in case of EIP-1967 proxy pattern" do + test "get_implementation_abi_from_proxy/2 returns implementation abi in case of EIP-1967 proxy pattern (logic contract)" do proxy_contract_address = insert(:contract_address) smart_contract = @@ -355,4 +367,103 @@ defmodule Explorer.Chain.SmartContract.ProxyTest do assert implementation_abi == @implementation_abi end end + + @beacon_abi [ + %{ + "type" => "function", + "stateMutability" => "view", + "outputs" => [%{"type" => "address", "name" => "", "internalType" => "address"}], + "name" => "implementation", + "inputs" => [] + } + ] + test "get_implementation_abi_from_proxy/2 returns implementation abi in case of EIP-1967 proxy pattern (beacon contract)" do + proxy_contract_address = insert(:contract_address) + + smart_contract = + insert(:smart_contract, address_hash: proxy_contract_address.hash, abi: [], contract_code_md5: "123") + + beacon_contract_address = insert(:contract_address) + + insert(:smart_contract, + address_hash: beacon_contract_address.hash, + abi: @beacon_abi, + contract_code_md5: "123" + ) + + beacon_contract_address_hash_string = Base.encode16(beacon_contract_address.hash.bytes, case: :lower) + + implementation_contract_address = insert(:contract_address) + + insert(:smart_contract, + address_hash: implementation_contract_address.hash, + abi: @implementation_abi, + contract_code_md5: "123" + ) + + implementation_contract_address_hash_string = + Base.encode16(implementation_contract_address.hash.bytes, case: :lower) + + EthereumJSONRPC.Mox + |> expect( + :json_rpc, + fn %{ + id: _id, + method: "eth_getStorageAt", + params: [ + _, + "0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc", + "latest" + ] + }, + _options -> + {:ok, "0x0000000000000000000000000000000000000000000000000000000000000000"} + end + ) + |> expect( + :json_rpc, + fn %{ + id: _id, + method: "eth_getStorageAt", + params: [ + _, + "0xa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6cb3582b35133d50", + "latest" + ] + }, + _options -> + {:ok, "0x000000000000000000000000" <> beacon_contract_address_hash_string} + end + ) + |> expect( + :json_rpc, + fn [ + %{ + id: _id, + method: "eth_call", + params: [ + %{data: "0x5c60da1b", to: "0x000000000000000000000000" <> ^beacon_contract_address_hash_string}, + "latest" + ] + } + ], + _options -> + { + :ok, + [ + %{ + id: _id, + jsonrpc: "2.0", + result: "0x000000000000000000000000" <> implementation_contract_address_hash_string + } + ] + } + end + ) + + implementation_abi = Proxy.get_implementation_abi_from_proxy(smart_contract, []) + verify!(EthereumJSONRPC.Mox) + + assert implementation_abi == @implementation_abi + end end diff --git a/apps/explorer/test/explorer/chain/smart_contract_test.exs b/apps/explorer/test/explorer/chain/smart_contract_test.exs index 631cffe3724d..f7c45251b0e4 100644 --- a/apps/explorer/test/explorer/chain/smart_contract_test.exs +++ b/apps/explorer/test/explorer/chain/smart_contract_test.exs @@ -75,30 +75,8 @@ defmodule Explorer.Chain.SmartContractTest do string_implementation_address_hash = to_string(implementation_smart_contract.address_hash) - expect(EthereumJSONRPC.Mox, :json_rpc, fn %{ - id: 0, - method: "eth_getStorageAt", - params: [ - _, - "0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc", - "latest" - ] - }, - _options -> - {:ok, "0x0000000000000000000000000000000000000000000000000000000000000000"} - end) - |> expect(:json_rpc, fn %{ - id: 0, - method: "eth_getStorageAt", - params: [ - _, - "0xa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6cb3582b35133d50", - "latest" - ] - }, - _options -> - {:ok, "0x0000000000000000000000000000000000000000000000000000000000000000"} - end) + mock_empty_logic_storage_pointer_request() + |> mock_empty_beacon_storage_pointer_request() |> expect(:json_rpc, fn %{ id: 0, method: "eth_getStorageAt", @@ -287,91 +265,16 @@ defmodule Explorer.Chain.SmartContractTest do end end - describe "address_hash_to_smart_contract/1" do - test "fetches a smart contract" do - smart_contract = insert(:smart_contract, contract_code_md5: "123") - - assert ^smart_contract = SmartContract.address_hash_to_smart_contract(smart_contract.address_hash) - end - end - def get_eip1967_implementation_zero_addresses do - EthereumJSONRPC.Mox - |> expect(:json_rpc, fn %{ - id: 0, - method: "eth_getStorageAt", - params: [ - _, - "0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc", - "latest" - ] - }, - _options -> - {:ok, "0x0000000000000000000000000000000000000000000000000000000000000000"} - end) - |> expect(:json_rpc, fn %{ - id: 0, - method: "eth_getStorageAt", - params: [ - _, - "0xa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6cb3582b35133d50", - "latest" - ] - }, - _options -> - {:ok, "0x0000000000000000000000000000000000000000000000000000000000000000"} - end) - |> expect(:json_rpc, fn %{ - id: 0, - method: "eth_getStorageAt", - params: [ - _, - "0x7050c9e0f4ca769c69bd3a8ef740bc37934f8e2c036e5a723fd8ee048ed3f8c3", - "latest" - ] - }, - _options -> - {:ok, "0x0000000000000000000000000000000000000000000000000000000000000000"} - end) - |> expect(:json_rpc, fn %{ - id: 0, - method: "eth_getStorageAt", - params: [ - _, - "0xc5f16f0fcc639fa48a6947836d9850f504798523bf8c9a3a87d5876cf622bcf7", - "latest" - ] - }, - _options -> - {:ok, "0x0000000000000000000000000000000000000000000000000000000000000000"} - end) + mock_empty_logic_storage_pointer_request() + |> mock_empty_beacon_storage_pointer_request() + |> mock_empty_oz_storage_pointer_request() + |> mock_empty_eip_1822_storage_pointer_request() end def get_eip1967_implementation_non_zero_address do - expect(EthereumJSONRPC.Mox, :json_rpc, fn %{ - id: 0, - method: "eth_getStorageAt", - params: [ - _, - "0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc", - "latest" - ] - }, - _options -> - {:ok, "0x0000000000000000000000000000000000000000000000000000000000000000"} - end) - |> expect(:json_rpc, fn %{ - id: 0, - method: "eth_getStorageAt", - params: [ - _, - "0xa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6cb3582b35133d50", - "latest" - ] - }, - _options -> - {:ok, "0x0000000000000000000000000000000000000000000000000000000000000000"} - end) + mock_empty_logic_storage_pointer_request() + |> mock_empty_beacon_storage_pointer_request() |> expect(:json_rpc, fn %{ id: 0, method: "eth_getStorageAt", @@ -400,42 +303,9 @@ defmodule Explorer.Chain.SmartContractTest do _options -> {:error, "error"} end) - |> expect(:json_rpc, fn %{ - id: 0, - method: "eth_getStorageAt", - params: [ - _, - "0xa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6cb3582b35133d50", - "latest" - ] - }, - _options -> - {:ok, "0x0000000000000000000000000000000000000000000000000000000000000000"} - end) - |> expect(:json_rpc, fn %{ - id: 0, - method: "eth_getStorageAt", - params: [ - _, - "0x7050c9e0f4ca769c69bd3a8ef740bc37934f8e2c036e5a723fd8ee048ed3f8c3", - "latest" - ] - }, - _options -> - {:ok, "0x0000000000000000000000000000000000000000000000000000000000000000"} - end) - |> expect(:json_rpc, fn %{ - id: 0, - method: "eth_getStorageAt", - params: [ - _, - "0xc5f16f0fcc639fa48a6947836d9850f504798523bf8c9a3a87d5876cf622bcf7", - "latest" - ] - }, - _options -> - {:ok, "0x0000000000000000000000000000000000000000000000000000000000000000"} - end) + |> mock_empty_beacon_storage_pointer_request() + |> mock_empty_oz_storage_pointer_request() + |> mock_empty_eip_1822_storage_pointer_request() end def assert_empty_implementation(address_hash) do @@ -893,6 +763,23 @@ defmodule Explorer.Chain.SmartContractTest do end defp expect_address_in_response(string_implementation_address_hash) do + mock_empty_logic_storage_pointer_request() + |> mock_empty_beacon_storage_pointer_request() + |> expect(:json_rpc, fn %{ + id: 0, + method: "eth_getStorageAt", + params: [ + _, + "0x7050c9e0f4ca769c69bd3a8ef740bc37934f8e2c036e5a723fd8ee048ed3f8c3", + "latest" + ] + }, + _options -> + {:ok, string_implementation_address_hash} + end) + end + + defp mock_empty_logic_storage_pointer_request do expect(EthereumJSONRPC.Mox, :json_rpc, fn %{ id: 0, method: "eth_getStorageAt", @@ -905,29 +792,50 @@ defmodule Explorer.Chain.SmartContractTest do _options -> {:ok, "0x0000000000000000000000000000000000000000000000000000000000000000"} end) - |> expect(:json_rpc, fn %{ - id: 0, - method: "eth_getStorageAt", - params: [ - _, - "0xa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6cb3582b35133d50", - "latest" - ] - }, - _options -> + end + + defp mock_empty_beacon_storage_pointer_request(mox) do + expect(mox, :json_rpc, fn %{ + id: 0, + method: "eth_getStorageAt", + params: [ + _, + "0xa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6cb3582b35133d50", + "latest" + ] + }, + _options -> {:ok, "0x0000000000000000000000000000000000000000000000000000000000000000"} end) - |> expect(:json_rpc, fn %{ - id: 0, - method: "eth_getStorageAt", - params: [ - _, - "0x7050c9e0f4ca769c69bd3a8ef740bc37934f8e2c036e5a723fd8ee048ed3f8c3", - "latest" - ] - }, - _options -> - {:ok, string_implementation_address_hash} + end + + defp mock_empty_eip_1822_storage_pointer_request(mox) do + expect(mox, :json_rpc, fn %{ + id: 0, + method: "eth_getStorageAt", + params: [ + _, + "0xc5f16f0fcc639fa48a6947836d9850f504798523bf8c9a3a87d5876cf622bcf7", + "latest" + ] + }, + _options -> + {:ok, "0x0000000000000000000000000000000000000000000000000000000000000000"} + end) + end + + defp mock_empty_oz_storage_pointer_request(mox) do + expect(mox, :json_rpc, fn %{ + id: 0, + method: "eth_getStorageAt", + params: [ + _, + "0x7050c9e0f4ca769c69bd3a8ef740bc37934f8e2c036e5a723fd8ee048ed3f8c3", + "latest" + ] + }, + _options -> + {:ok, "0x0000000000000000000000000000000000000000000000000000000000000000"} end) end end diff --git a/apps/explorer/test/explorer/chain/token/instance_test.exs b/apps/explorer/test/explorer/chain/token/instance_test.exs new file mode 100644 index 000000000000..96f78e79fcc6 --- /dev/null +++ b/apps/explorer/test/explorer/chain/token/instance_test.exs @@ -0,0 +1,83 @@ +defmodule Explorer.Chain.Token.InstanceTest do + use Explorer.DataCase + + alias Explorer.Repo + alias Explorer.Chain.Token.Instance + + describe "stream_not_inserted_token_instances/2" do + test "reduces with given reducer and accumulator for ERC-721 token" do + token_contract_address = insert(:contract_address) + token = insert(:token, contract_address: token_contract_address, type: "ERC-721") + + transaction = + :transaction + |> insert() + |> with_block(insert(:block, number: 1)) + + token_transfer = + insert( + :token_transfer, + block_number: 1000, + to_address: build(:address), + transaction: transaction, + token_contract_address: token_contract_address, + token: token, + token_ids: [11] + ) + + assert [result] = 5 |> Instance.not_inserted_token_instances_query() |> Repo.all() + assert result.token_id == List.first(token_transfer.token_ids) + assert result.contract_address_hash == token_transfer.token_contract_address_hash + end + + test "does not fetch token transfers without token_ids" do + token_contract_address = insert(:contract_address) + token = insert(:token, contract_address: token_contract_address, type: "ERC-721") + + transaction = + :transaction + |> insert() + |> with_block(insert(:block, number: 1)) + + insert( + :token_transfer, + block_number: 1000, + to_address: build(:address), + transaction: transaction, + token_contract_address: token_contract_address, + token: token, + token_ids: nil + ) + + assert [] = 5 |> Instance.not_inserted_token_instances_query() |> Repo.all() + end + + test "do not fetch records with token instances" do + token_contract_address = insert(:contract_address) + token = insert(:token, contract_address: token_contract_address, type: "ERC-721") + + transaction = + :transaction + |> insert() + |> with_block(insert(:block, number: 1)) + + token_transfer = + insert( + :token_transfer, + block_number: 1000, + to_address: build(:address), + transaction: transaction, + token_contract_address: token_contract_address, + token: token, + token_ids: [11] + ) + + insert(:token_instance, + token_id: List.first(token_transfer.token_ids), + token_contract_address_hash: token_transfer.token_contract_address_hash + ) + + assert [] = 5 |> Instance.not_inserted_token_instances_query() |> Repo.all() + end + end +end diff --git a/apps/explorer/test/explorer/chain/token_transfer_test.exs b/apps/explorer/test/explorer/chain/token_transfer_test.exs index 29165457b773..3139f318cb39 100644 --- a/apps/explorer/test/explorer/chain/token_transfer_test.exs +++ b/apps/explorer/test/explorer/chain/token_transfer_test.exs @@ -325,4 +325,28 @@ defmodule Explorer.Chain.TokenTransferTest do assert Enum.member?(page_two, transaction_one_bytes) == true end end + + describe "uncataloged_token_transfer_block_numbers/0" do + test "returns a list of block numbers" do + block = insert(:block) + address = insert(:address) + + log = + insert(:token_transfer_log, + transaction: + insert(:transaction, + block_number: block.number, + block_hash: block.hash, + cumulative_gas_used: 0, + gas_used: 0, + index: 0 + ), + block: block, + address_hash: address.hash + ) + + block_number = log.block_number + assert {:ok, [^block_number]} = TokenTransfer.uncataloged_token_transfer_block_numbers() + end + end end diff --git a/apps/explorer/test/explorer/chain/transaction_test.exs b/apps/explorer/test/explorer/chain/transaction_test.exs index d204d2822325..7a5ce5bbe51a 100644 --- a/apps/explorer/test/explorer/chain/transaction_test.exs +++ b/apps/explorer/test/explorer/chain/transaction_test.exs @@ -4,10 +4,13 @@ defmodule Explorer.Chain.TransactionTest do import Mox alias Ecto.Changeset - alias Explorer.Chain.Transaction + alias Explorer.Chain.{Address, InternalTransaction, Transaction} + alias Explorer.PagingOptions doctest Transaction + setup :set_mox_global + setup :verify_on_exit! describe "changeset/2" do @@ -311,6 +314,480 @@ defmodule Explorer.Chain.TransactionTest do end end + describe "address_to_transactions_tasks_range_of_blocks/2" do + test "returns empty extremums if no transactions" do + address = insert(:address) + + extremums = Transaction.address_to_transactions_tasks_range_of_blocks(address.hash, []) + + assert extremums == %{ + :min_block_number => nil, + :max_block_number => 0 + } + end + + test "returns correct extremums for from_address" do + address = insert(:address) + + :transaction + |> insert(from_address: address) + |> with_block(insert(:block, number: 1000)) + + extremums = Transaction.address_to_transactions_tasks_range_of_blocks(address.hash, []) + + assert extremums == %{ + :min_block_number => 1000, + :max_block_number => 1000 + } + end + + test "returns correct extremums for to_address" do + address = insert(:address) + + :transaction + |> insert(to_address: address) + |> with_block(insert(:block, number: 1000)) + + extremums = Transaction.address_to_transactions_tasks_range_of_blocks(address.hash, []) + + assert extremums == %{ + :min_block_number => 1000, + :max_block_number => 1000 + } + end + + test "returns correct extremums for created_contract_address" do + address = insert(:address) + + :transaction + |> insert(created_contract_address: address) + |> with_block(insert(:block, number: 1000)) + + extremums = Transaction.address_to_transactions_tasks_range_of_blocks(address.hash, []) + + assert extremums == %{ + :min_block_number => 1000, + :max_block_number => 1000 + } + end + + test "returns correct extremums for multiple number of transactions" do + address = insert(:address) + + :transaction + |> insert(created_contract_address: address) + |> with_block(insert(:block, number: 1000)) + + :transaction + |> insert(created_contract_address: address) + |> with_block(insert(:block, number: 999)) + + :transaction + |> insert(created_contract_address: address) + |> with_block(insert(:block, number: 1003)) + + :transaction + |> insert(from_address: address) + |> with_block(insert(:block, number: 1001)) + + :transaction + |> insert(from_address: address) + |> with_block(insert(:block, number: 1004)) + + :transaction + |> insert(to_address: address) + |> with_block(insert(:block, number: 1002)) + + :transaction + |> insert(to_address: address) + |> with_block(insert(:block, number: 998)) + + extremums = Transaction.address_to_transactions_tasks_range_of_blocks(address.hash, []) + + assert extremums == %{ + :min_block_number => 998, + :max_block_number => 1004 + } + end + end + + describe "address_to_transactions_with_rewards/2" do + test "without transactions" do + %Address{hash: address_hash} = insert(:address) + + assert Repo.aggregate(Transaction, :count, :hash) == 0 + + assert [] == Transaction.address_to_transactions_with_rewards(address_hash) + end + + test "with from transactions" do + %Address{hash: address_hash} = address = insert(:address) + + transaction = + :transaction + |> insert(from_address: address) + |> with_block() + + assert [transaction] == + Transaction.address_to_transactions_with_rewards(address_hash, direction: :from) + |> Repo.preload([:block, :to_address, :from_address]) + end + + test "with to transactions" do + %Address{hash: address_hash} = address = insert(:address) + + transaction = + :transaction + |> insert(to_address: address) + |> with_block() + + assert [transaction] == + Transaction.address_to_transactions_with_rewards(address_hash, direction: :to) + |> Repo.preload([:block, :to_address, :from_address]) + end + + test "with to and from transactions and direction: :from" do + %Address{hash: address_hash} = address = insert(:address) + + transaction = + :transaction + |> insert(from_address: address) + |> with_block() + + # only contains "from" transaction + assert [transaction] == + Transaction.address_to_transactions_with_rewards(address_hash, direction: :from) + |> Repo.preload([:block, :to_address, :from_address]) + end + + test "with to and from transactions and direction: :to" do + %Address{hash: address_hash} = address = insert(:address) + + transaction = + :transaction + |> insert(to_address: address) + |> with_block() + + assert [transaction] == + Transaction.address_to_transactions_with_rewards(address_hash, direction: :to) + |> Repo.preload([:block, :to_address, :from_address]) + end + + test "with to and from transactions and no :direction option" do + %Address{hash: address_hash} = address = insert(:address) + block = insert(:block) + + transaction1 = + :transaction + |> insert(to_address: address) + |> with_block(block) + + transaction2 = + :transaction + |> insert(from_address: address) + |> with_block(block) + + assert [transaction2, transaction1] == + Transaction.address_to_transactions_with_rewards(address_hash) + |> Repo.preload([:block, :to_address, :from_address]) + end + + test "does not include non-contract-creation parent transactions" do + transaction = + %Transaction{} = + :transaction + |> insert() + |> with_block() + + %InternalTransaction{created_contract_address: address} = + insert(:internal_transaction_create, + transaction: transaction, + index: 0, + block_number: transaction.block_number, + block_hash: transaction.block_hash, + block_index: 0, + transaction_index: transaction.index + ) + + assert [] == Transaction.address_to_transactions_with_rewards(address.hash) + end + + test "returns transactions that have token transfers for the given to_address" do + %Address{hash: address_hash} = address = insert(:address) + + transaction = + :transaction + |> insert(to_address: address, to_address_hash: address.hash) + |> with_block() + + insert( + :token_transfer, + to_address: address, + transaction: transaction + ) + + assert [transaction.hash] == + Transaction.address_to_transactions_with_rewards(address_hash) + |> Enum.map(& &1.hash) + end + + test "with transactions can be paginated" do + %Address{hash: address_hash} = address = insert(:address) + + second_page_hashes = + 2 + |> insert_list(:transaction, from_address: address) + |> with_block() + |> Enum.map(& &1.hash) + + %Transaction{block_number: block_number, index: index} = + :transaction + |> insert(from_address: address) + |> with_block() + + assert second_page_hashes == + address_hash + |> Transaction.address_to_transactions_with_rewards( + paging_options: %PagingOptions{ + key: {block_number, index}, + page_size: 2 + } + ) + |> Enum.map(& &1.hash) + |> Enum.reverse() + end + + test "returns results in reverse chronological order by block number and transaction index" do + %Address{hash: address_hash} = address = insert(:address) + + a_block = insert(:block, number: 6000) + + %Transaction{hash: first} = + :transaction + |> insert(to_address: address) + |> with_block(a_block) + + %Transaction{hash: second} = + :transaction + |> insert(to_address: address) + |> with_block(a_block) + + %Transaction{hash: third} = + :transaction + |> insert(to_address: address) + |> with_block(a_block) + + %Transaction{hash: fourth} = + :transaction + |> insert(to_address: address) + |> with_block(a_block) + + b_block = insert(:block, number: 2000) + + %Transaction{hash: fifth} = + :transaction + |> insert(to_address: address) + |> with_block(b_block) + + %Transaction{hash: sixth} = + :transaction + |> insert(to_address: address) + |> with_block(b_block) + + result = + address_hash + |> Transaction.address_to_transactions_with_rewards() + |> Enum.map(& &1.hash) + + assert [fourth, third, second, first, sixth, fifth] == result + end + + test "with emission rewards" do + Application.put_env(:block_scout_web, BlockScoutWeb.Chain, has_emission_funds: true) + + Application.put_env(:explorer, Explorer.Chain.Block.Reward, + validators_contract_address: "0x0000000000000000000000000000000000000005", + keys_manager_contract_address: "0x0000000000000000000000000000000000000006" + ) + + consumer_pid = start_supervised!(Explorer.Chain.Fetcher.FetchValidatorInfoOnDemand) + :erlang.trace(consumer_pid, true, [:receive]) + + block = insert(:block) + + block_miner_hash_string = Base.encode16(block.miner_hash.bytes, case: :lower) + block_miner_hash = block.miner_hash + + insert( + :reward, + address_hash: block.miner_hash, + block_hash: block.hash, + address_type: :validator + ) + + insert( + :reward, + address_hash: block.miner_hash, + block_hash: block.hash, + address_type: :emission_funds + ) + + # isValidator => true + expect( + EthereumJSONRPC.Mox, + :json_rpc, + fn [%{id: id, method: _, params: [%{data: _, to: _}, _]}], _options -> + {:ok, + [%{id: id, jsonrpc: "2.0", result: "0x0000000000000000000000000000000000000000000000000000000000000001"}]} + end + ) + + # getPayoutByMining => 0x0000000000000000000000000000000000000001 + expect( + EthereumJSONRPC.Mox, + :json_rpc, + fn [%{id: id, method: _, params: [%{data: _, to: _}, _]}], _options -> + {:ok, [%{id: id, result: "0x000000000000000000000000" <> block_miner_hash_string}]} + end + ) + + res = Transaction.address_to_transactions_with_rewards(block.miner.hash) + assert [{_, _}] = res + + assert_receive {:trace, ^consumer_pid, :receive, {:"$gen_cast", {:fetch_or_update, ^block_miner_hash}}}, 1000 + :timer.sleep(500) + + on_exit(fn -> + Application.put_env(:block_scout_web, BlockScoutWeb.Chain, has_emission_funds: false) + + Application.put_env(:explorer, Explorer.Chain.Block.Reward, + validators_contract_address: nil, + keys_manager_contract_address: nil + ) + end) + end + + test "with emission rewards and transactions" do + Application.put_env(:block_scout_web, BlockScoutWeb.Chain, has_emission_funds: true) + + Application.put_env(:explorer, Explorer.Chain.Block.Reward, + validators_contract_address: "0x0000000000000000000000000000000000000005", + keys_manager_contract_address: "0x0000000000000000000000000000000000000006" + ) + + consumer_pid = start_supervised!(Explorer.Chain.Fetcher.FetchValidatorInfoOnDemand) + :erlang.trace(consumer_pid, true, [:receive]) + + block = insert(:block) + + block_miner_hash_string = Base.encode16(block.miner_hash.bytes, case: :lower) + block_miner_hash = block.miner_hash + + insert( + :reward, + address_hash: block.miner_hash, + block_hash: block.hash, + address_type: :validator + ) + + insert( + :reward, + address_hash: block.miner_hash, + block_hash: block.hash, + address_type: :emission_funds + ) + + :transaction + |> insert(to_address: block.miner) + |> with_block(block) + |> Repo.preload(:token_transfers) + + # isValidator => true + expect( + EthereumJSONRPC.Mox, + :json_rpc, + fn [%{id: id, method: _, params: [%{data: _, to: _}, _]}], _options -> + {:ok, + [%{id: id, jsonrpc: "2.0", result: "0x0000000000000000000000000000000000000000000000000000000000000001"}]} + end + ) + + # getPayoutByMining => 0x0000000000000000000000000000000000000001 + expect( + EthereumJSONRPC.Mox, + :json_rpc, + fn [%{id: id, method: _, params: [%{data: _, to: _}, _]}], _options -> + {:ok, [%{id: id, result: "0x000000000000000000000000" <> block_miner_hash_string}]} + end + ) + + assert [_, {_, _}] = Transaction.address_to_transactions_with_rewards(block.miner.hash, direction: :to) + + assert_receive {:trace, ^consumer_pid, :receive, {:"$gen_cast", {:fetch_or_update, ^block_miner_hash}}}, 1000 + :timer.sleep(500) + + on_exit(fn -> + Application.put_env(:block_scout_web, BlockScoutWeb.Chain, has_emission_funds: false) + + Application.put_env(:explorer, Explorer.Chain.Block.Reward, + validators_contract_address: nil, + keys_manager_contract_address: nil + ) + end) + end + + test "with transactions if rewards are not in the range of blocks" do + Application.put_env(:block_scout_web, BlockScoutWeb.Chain, has_emission_funds: true) + + block = insert(:block) + + insert( + :reward, + address_hash: block.miner_hash, + block_hash: block.hash, + address_type: :validator + ) + + insert( + :reward, + address_hash: block.miner_hash, + block_hash: block.hash, + address_type: :emission_funds + ) + + :transaction + |> insert(from_address: block.miner) + |> with_block() + |> Repo.preload(:token_transfers) + + assert [_] = Transaction.address_to_transactions_with_rewards(block.miner.hash, direction: :from) + + Application.put_env(:block_scout_web, BlockScoutWeb.Chain, has_emission_funds: false) + end + + test "with emissions rewards, but feature disabled" do + Application.put_env(:block_scout_web, BlockScoutWeb.Chain, has_emission_funds: false) + + block = insert(:block) + + insert( + :reward, + address_hash: block.miner_hash, + block_hash: block.hash, + address_type: :validator + ) + + insert( + :reward, + address_hash: block.miner_hash, + block_hash: block.hash, + address_type: :emission_funds + ) + + assert [] == Transaction.address_to_transactions_with_rewards(block.miner.hash) + end + end + # EIP-1967 + EIP-1822 defp request_zero_implementations do EthereumJSONRPC.Mox diff --git a/apps/explorer/test/explorer/chain_test.exs b/apps/explorer/test/explorer/chain_test.exs index 96a787543a8c..fb529bb074c6 100644 --- a/apps/explorer/test/explorer/chain_test.exs +++ b/apps/explorer/test/explorer/chain_test.exs @@ -23,7 +23,6 @@ defmodule Explorer.ChainTest do Token, TokenTransfer, Transaction, - SmartContract, Wei } @@ -38,6 +37,10 @@ defmodule Explorer.ChainTest do alias Explorer.Counters.AddressesWithBalanceCounter alias Explorer.Counters.AddressesCounter + @first_topic_hex_string "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef" + @second_topic_hex_string "0x000000000000000000000000e8ddc5c7a2d2f0d7a9798459c0104fdf5e987aca" + @third_topic_hex_string "0x000000000000000000000000515c09c5bba1ed566b02a5b0599ec5d5d0aee73d" + doctest Explorer.Chain setup :set_mox_global @@ -316,6 +319,9 @@ defmodule Explorer.ChainTest do block_number: transaction1.block_number ) + first_topic_hex_string = "0x7fcf532c15f0a6db0bd6d0e038bea71d30d808c7d98cb3bf7268a95bf5081b65" + {:ok, first_topic} = Explorer.Chain.Hash.Full.cast(first_topic_hex_string) + transaction2 = :transaction |> insert(from_address: address) @@ -326,11 +332,11 @@ defmodule Explorer.ChainTest do transaction: transaction2, index: 2, address: address, - first_topic: "test", + first_topic: first_topic, block_number: transaction2.block_number ) - [found_log] = Chain.address_to_logs(address_hash, false, topic: "test") + [found_log] = Chain.address_to_logs(address_hash, false, topic: first_topic_hex_string) assert found_log.transaction.hash == transaction2.hash end @@ -343,13 +349,16 @@ defmodule Explorer.ChainTest do |> insert(to_address: address) |> with_block() + fourth_topic_hex_string = "0x927abf391899d10d331079a63caffa905efa7075a44a7bbd52b190db4c4308fb" + {:ok, fourth_topic} = Explorer.Chain.Hash.Full.cast(fourth_topic_hex_string) + insert(:log, block: transaction1.block, block_number: transaction1.block_number, transaction: transaction1, index: 1, address: address, - fourth_topic: "test" + fourth_topic: fourth_topic ) transaction2 = @@ -365,486 +374,12 @@ defmodule Explorer.ChainTest do address: address ) - [found_log] = Chain.address_to_logs(address_hash, false, topic: "test") + [found_log] = Chain.address_to_logs(address_hash, false, topic: fourth_topic_hex_string) assert found_log.transaction.hash == transaction1.hash end end - describe "address_to_transactions_with_rewards/2" do - test "without transactions" do - %Address{hash: address_hash} = insert(:address) - - assert Repo.aggregate(Transaction, :count, :hash) == 0 - - assert [] == Chain.address_to_transactions_with_rewards(address_hash) - end - - test "with from transactions" do - %Address{hash: address_hash} = address = insert(:address) - - transaction = - :transaction - |> insert(from_address: address) - |> with_block() - - assert [transaction] == - Chain.address_to_transactions_with_rewards(address_hash, direction: :from) - |> Repo.preload([:block, :to_address, :from_address]) - end - - test "with to transactions" do - %Address{hash: address_hash} = address = insert(:address) - - transaction = - :transaction - |> insert(to_address: address) - |> with_block() - - assert [transaction] == - Chain.address_to_transactions_with_rewards(address_hash, direction: :to) - |> Repo.preload([:block, :to_address, :from_address]) - end - - test "with to and from transactions and direction: :from" do - %Address{hash: address_hash} = address = insert(:address) - - transaction = - :transaction - |> insert(from_address: address) - |> with_block() - - # only contains "from" transaction - assert [transaction] == - Chain.address_to_transactions_with_rewards(address_hash, direction: :from) - |> Repo.preload([:block, :to_address, :from_address]) - end - - test "with to and from transactions and direction: :to" do - %Address{hash: address_hash} = address = insert(:address) - - transaction = - :transaction - |> insert(to_address: address) - |> with_block() - - assert [transaction] == - Chain.address_to_transactions_with_rewards(address_hash, direction: :to) - |> Repo.preload([:block, :to_address, :from_address]) - end - - test "with to and from transactions and no :direction option" do - %Address{hash: address_hash} = address = insert(:address) - block = insert(:block) - - transaction1 = - :transaction - |> insert(to_address: address) - |> with_block(block) - - transaction2 = - :transaction - |> insert(from_address: address) - |> with_block(block) - - assert [transaction2, transaction1] == - Chain.address_to_transactions_with_rewards(address_hash) - |> Repo.preload([:block, :to_address, :from_address]) - end - - test "does not include non-contract-creation parent transactions" do - transaction = - %Transaction{} = - :transaction - |> insert() - |> with_block() - - %InternalTransaction{created_contract_address: address} = - insert(:internal_transaction_create, - transaction: transaction, - index: 0, - block_number: transaction.block_number, - block_hash: transaction.block_hash, - block_index: 0, - transaction_index: transaction.index - ) - - assert [] == Chain.address_to_transactions_with_rewards(address.hash) - end - - test "returns transactions that have token transfers for the given to_address" do - %Address{hash: address_hash} = address = insert(:address) - - transaction = - :transaction - |> insert(to_address: address, to_address_hash: address.hash) - |> with_block() - - insert( - :token_transfer, - to_address: address, - transaction: transaction - ) - - assert [transaction.hash] == - Chain.address_to_transactions_with_rewards(address_hash) - |> Enum.map(& &1.hash) - end - - test "with transactions can be paginated" do - %Address{hash: address_hash} = address = insert(:address) - - second_page_hashes = - 2 - |> insert_list(:transaction, from_address: address) - |> with_block() - |> Enum.map(& &1.hash) - - %Transaction{block_number: block_number, index: index} = - :transaction - |> insert(from_address: address) - |> with_block() - - assert second_page_hashes == - address_hash - |> Chain.address_to_transactions_with_rewards( - paging_options: %PagingOptions{ - key: {block_number, index}, - page_size: 2 - } - ) - |> Enum.map(& &1.hash) - |> Enum.reverse() - end - - test "returns results in reverse chronological order by block number and transaction index" do - %Address{hash: address_hash} = address = insert(:address) - - a_block = insert(:block, number: 6000) - - %Transaction{hash: first} = - :transaction - |> insert(to_address: address) - |> with_block(a_block) - - %Transaction{hash: second} = - :transaction - |> insert(to_address: address) - |> with_block(a_block) - - %Transaction{hash: third} = - :transaction - |> insert(to_address: address) - |> with_block(a_block) - - %Transaction{hash: fourth} = - :transaction - |> insert(to_address: address) - |> with_block(a_block) - - b_block = insert(:block, number: 2000) - - %Transaction{hash: fifth} = - :transaction - |> insert(to_address: address) - |> with_block(b_block) - - %Transaction{hash: sixth} = - :transaction - |> insert(to_address: address) - |> with_block(b_block) - - result = - address_hash - |> Chain.address_to_transactions_with_rewards() - |> Enum.map(& &1.hash) - - assert [fourth, third, second, first, sixth, fifth] == result - end - - test "with emission rewards" do - Application.put_env(:block_scout_web, BlockScoutWeb.Chain, has_emission_funds: true) - - Application.put_env(:explorer, Explorer.Chain.Block.Reward, - validators_contract_address: "0x0000000000000000000000000000000000000005", - keys_manager_contract_address: "0x0000000000000000000000000000000000000006" - ) - - consumer_pid = start_supervised!(Explorer.Chain.Fetcher.FetchValidatorInfoOnDemand) - :erlang.trace(consumer_pid, true, [:receive]) - - block = insert(:block) - - block_miner_hash_string = Base.encode16(block.miner_hash.bytes, case: :lower) - block_miner_hash = block.miner_hash - - insert( - :reward, - address_hash: block.miner_hash, - block_hash: block.hash, - address_type: :validator - ) - - insert( - :reward, - address_hash: block.miner_hash, - block_hash: block.hash, - address_type: :emission_funds - ) - - # isValidator => true - expect( - EthereumJSONRPC.Mox, - :json_rpc, - fn [%{id: id, method: _, params: [%{data: _, to: _}, _]}], _options -> - {:ok, - [%{id: id, jsonrpc: "2.0", result: "0x0000000000000000000000000000000000000000000000000000000000000001"}]} - end - ) - - # getPayoutByMining => 0x0000000000000000000000000000000000000001 - expect( - EthereumJSONRPC.Mox, - :json_rpc, - fn [%{id: id, method: _, params: [%{data: _, to: _}, _]}], _options -> - {:ok, [%{id: id, result: "0x000000000000000000000000" <> block_miner_hash_string}]} - end - ) - - res = Chain.address_to_transactions_with_rewards(block.miner.hash) - assert [{_, _}] = res - - assert_receive {:trace, ^consumer_pid, :receive, {:"$gen_cast", {:fetch_or_update, ^block_miner_hash}}}, 1000 - :timer.sleep(500) - - on_exit(fn -> - Application.put_env(:block_scout_web, BlockScoutWeb.Chain, has_emission_funds: false) - - Application.put_env(:explorer, Explorer.Chain.Block.Reward, - validators_contract_address: nil, - keys_manager_contract_address: nil - ) - end) - end - - test "with emission rewards and transactions" do - Application.put_env(:block_scout_web, BlockScoutWeb.Chain, has_emission_funds: true) - - Application.put_env(:explorer, Explorer.Chain.Block.Reward, - validators_contract_address: "0x0000000000000000000000000000000000000005", - keys_manager_contract_address: "0x0000000000000000000000000000000000000006" - ) - - consumer_pid = start_supervised!(Explorer.Chain.Fetcher.FetchValidatorInfoOnDemand) - :erlang.trace(consumer_pid, true, [:receive]) - - block = insert(:block) - - block_miner_hash_string = Base.encode16(block.miner_hash.bytes, case: :lower) - block_miner_hash = block.miner_hash - - insert( - :reward, - address_hash: block.miner_hash, - block_hash: block.hash, - address_type: :validator - ) - - insert( - :reward, - address_hash: block.miner_hash, - block_hash: block.hash, - address_type: :emission_funds - ) - - :transaction - |> insert(to_address: block.miner) - |> with_block(block) - |> Repo.preload(:token_transfers) - - # isValidator => true - expect( - EthereumJSONRPC.Mox, - :json_rpc, - fn [%{id: id, method: _, params: [%{data: _, to: _}, _]}], _options -> - {:ok, - [%{id: id, jsonrpc: "2.0", result: "0x0000000000000000000000000000000000000000000000000000000000000001"}]} - end - ) - - # getPayoutByMining => 0x0000000000000000000000000000000000000001 - expect( - EthereumJSONRPC.Mox, - :json_rpc, - fn [%{id: id, method: _, params: [%{data: _, to: _}, _]}], _options -> - {:ok, [%{id: id, result: "0x000000000000000000000000" <> block_miner_hash_string}]} - end - ) - - assert [_, {_, _}] = Chain.address_to_transactions_with_rewards(block.miner.hash, direction: :to) - - assert_receive {:trace, ^consumer_pid, :receive, {:"$gen_cast", {:fetch_or_update, ^block_miner_hash}}}, 1000 - :timer.sleep(500) - - on_exit(fn -> - Application.put_env(:block_scout_web, BlockScoutWeb.Chain, has_emission_funds: false) - - Application.put_env(:explorer, Explorer.Chain.Block.Reward, - validators_contract_address: nil, - keys_manager_contract_address: nil - ) - end) - end - - test "with transactions if rewards are not in the range of blocks" do - Application.put_env(:block_scout_web, BlockScoutWeb.Chain, has_emission_funds: true) - - block = insert(:block) - - insert( - :reward, - address_hash: block.miner_hash, - block_hash: block.hash, - address_type: :validator - ) - - insert( - :reward, - address_hash: block.miner_hash, - block_hash: block.hash, - address_type: :emission_funds - ) - - :transaction - |> insert(from_address: block.miner) - |> with_block() - |> Repo.preload(:token_transfers) - - assert [_] = Chain.address_to_transactions_with_rewards(block.miner.hash, direction: :from) - - Application.put_env(:block_scout_web, BlockScoutWeb.Chain, has_emission_funds: false) - end - - test "with emissions rewards, but feature disabled" do - Application.put_env(:block_scout_web, BlockScoutWeb.Chain, has_emission_funds: false) - - block = insert(:block) - - insert( - :reward, - address_hash: block.miner_hash, - block_hash: block.hash, - address_type: :validator - ) - - insert( - :reward, - address_hash: block.miner_hash, - block_hash: block.hash, - address_type: :emission_funds - ) - - assert [] == Chain.address_to_transactions_with_rewards(block.miner.hash) - end - end - - describe "address_to_transactions_tasks_range_of_blocks/2" do - test "returns empty extremums if no transactions" do - address = insert(:address) - - extremums = Chain.address_to_transactions_tasks_range_of_blocks(address.hash, []) - - assert extremums == %{ - :min_block_number => nil, - :max_block_number => 0 - } - end - - test "returns correct extremums for from_address" do - address = insert(:address) - - :transaction - |> insert(from_address: address) - |> with_block(insert(:block, number: 1000)) - - extremums = Chain.address_to_transactions_tasks_range_of_blocks(address.hash, []) - - assert extremums == %{ - :min_block_number => 1000, - :max_block_number => 1000 - } - end - - test "returns correct extremums for to_address" do - address = insert(:address) - - :transaction - |> insert(to_address: address) - |> with_block(insert(:block, number: 1000)) - - extremums = Chain.address_to_transactions_tasks_range_of_blocks(address.hash, []) - - assert extremums == %{ - :min_block_number => 1000, - :max_block_number => 1000 - } - end - - test "returns correct extremums for created_contract_address" do - address = insert(:address) - - :transaction - |> insert(created_contract_address: address) - |> with_block(insert(:block, number: 1000)) - - extremums = Chain.address_to_transactions_tasks_range_of_blocks(address.hash, []) - - assert extremums == %{ - :min_block_number => 1000, - :max_block_number => 1000 - } - end - - test "returns correct extremums for multiple number of transactions" do - address = insert(:address) - - :transaction - |> insert(created_contract_address: address) - |> with_block(insert(:block, number: 1000)) - - :transaction - |> insert(created_contract_address: address) - |> with_block(insert(:block, number: 999)) - - :transaction - |> insert(created_contract_address: address) - |> with_block(insert(:block, number: 1003)) - - :transaction - |> insert(from_address: address) - |> with_block(insert(:block, number: 1001)) - - :transaction - |> insert(from_address: address) - |> with_block(insert(:block, number: 1004)) - - :transaction - |> insert(to_address: address) - |> with_block(insert(:block, number: 1002)) - - :transaction - |> insert(to_address: address) - |> with_block(insert(:block, number: 998)) - - extremums = Chain.address_to_transactions_tasks_range_of_blocks(address.hash, []) - - assert extremums == %{ - :min_block_number => 998, - :max_block_number => 1004 - } - end - end - describe "total_transactions_sent_by_address/1" do test "increments +1 in the last nonce result" do address = insert(:address) @@ -1712,6 +1247,10 @@ defmodule Explorer.ChainTest do # Full tests in `test/explorer/import_test.exs` describe "import/1" do + {:ok, first_topic} = Explorer.Chain.Hash.Full.cast(@first_topic_hex_string) + {:ok, second_topic} = Explorer.Chain.Hash.Full.cast(@second_topic_hex_string) + {:ok, third_topic} = Explorer.Chain.Hash.Full.cast(@third_topic_hex_string) + @import_data %{ blocks: %{ params: [ @@ -1767,13 +1306,12 @@ defmodule Explorer.ChainTest do block_hash: "0xf6b4b8c88df3ebd252ec476328334dc026cf66606a84fb769b3d3cbccc8471bd", address_hash: "0x8bf38d4764929064f2d4d3a56520a76ab3df415b", data: "0x0000000000000000000000000000000000000000000000000de0b6b3a7640000", - first_topic: "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", - second_topic: "0x000000000000000000000000e8ddc5c7a2d2f0d7a9798459c0104fdf5e987aca", - third_topic: "0x000000000000000000000000515c09c5bba1ed566b02a5b0599ec5d5d0aee73d", + first_topic: first_topic, + second_topic: second_topic, + third_topic: third_topic, fourth_topic: nil, index: 0, - transaction_hash: "0x53bd884872de3e488692881baeec262e7b95234d3965248c39fe992fffd433e5", - type: "mined" + transaction_hash: "0x53bd884872de3e488692881baeec262e7b95234d3965248c39fe992fffd433e5" } ] }, @@ -1836,6 +1374,9 @@ defmodule Explorer.ChainTest do } test "with valid data" do + {:ok, first_topic} = Explorer.Chain.Hash.Full.cast(@first_topic_hex_string) + {:ok, second_topic} = Explorer.Chain.Hash.Full.cast(@second_topic_hex_string) + {:ok, third_topic} = Explorer.Chain.Hash.Full.cast(@third_topic_hex_string) difficulty = Decimal.new(340_282_366_920_938_463_463_374_607_431_768_211_454) total_difficulty = Decimal.new(12_590_447_576_074_723_148_144_860_474_975_121_280_509) token_transfer_amount = Decimal.new(1_000_000_000_000_000_000) @@ -1938,9 +1479,9 @@ defmodule Explorer.ChainTest do 167, 100, 0, 0>> }, index: 0, - first_topic: "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", - second_topic: "0x000000000000000000000000e8ddc5c7a2d2f0d7a9798459c0104fdf5e987aca", - third_topic: "0x000000000000000000000000515c09c5bba1ed566b02a5b0599ec5d5d0aee73d", + first_topic: ^first_topic, + second_topic: ^second_topic, + third_topic: ^third_topic, fourth_topic: nil, transaction_hash: %Hash{ byte_count: 32, @@ -1948,7 +1489,6 @@ defmodule Explorer.ChainTest do <<83, 189, 136, 72, 114, 222, 62, 72, 134, 146, 136, 27, 174, 236, 38, 46, 123, 149, 35, 77, 57, 101, 36, 140, 57, 254, 153, 47, 255, 212, 51, 229>> }, - type: "mined", inserted_at: %{}, updated_at: %{} } @@ -3607,67 +3147,29 @@ defmodule Explorer.ChainTest do end end - describe "block_reward_by_parts/1" do - setup do - {:ok, emission_reward: insert(:emission_reward)} - end - - test "without uncles", %{emission_reward: %{reward: reward, block_range: range}} do - block = build(:block, number: range.from, base_fee_per_gas: 5, uncles: []) - - tx1 = build(:transaction, gas_price: 1, gas_used: 1, block_number: block.number, block_hash: block.hash) - tx2 = build(:transaction, gas_price: 1, gas_used: 2, block_number: block.number, block_hash: block.hash) - - tx3 = - build(:transaction, - gas_price: 1, - gas_used: 3, - block_number: block.number, - block_hash: block.hash, - max_priority_fee_per_gas: 1 - ) - - expected_txn_fees = %Wei{value: Decimal.new(6)} - expected_burned_fees = %Wei{value: Decimal.new(30)} - expected_uncle_reward = %Wei{value: Decimal.new(0)} - - assert %{ - static_reward: ^reward, - txn_fees: ^expected_txn_fees, - burned_fees: ^expected_burned_fees, - uncle_reward: ^expected_uncle_reward - } = Chain.block_reward_by_parts(block, [tx1, tx2, tx3]) - end - - test "with uncles", %{emission_reward: %{reward: reward, block_range: range}} do - block = - build(:block, number: range.from, uncles: ["0xe670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d15273311"]) - - expected_uncle_reward = Wei.mult(reward, Decimal.from_float(1 / 32)) - - assert %{uncle_reward: ^expected_uncle_reward} = Chain.block_reward_by_parts(block, []) - end - end - describe "gas_payment_by_block_hash/1" do setup do number = 1 - %{consensus_block: insert(:block, number: number, consensus: true), number: number} + block = insert(:block, number: number, consensus: true) + + %{consensus_block: block, number: number} end - test "without consensus block hash has no key", %{consensus_block: consensus_block, number: number} do + test "without consensus block hash has key with 0 value", %{consensus_block: consensus_block, number: number} do non_consensus_block = insert(:block, number: number, consensus: false) :transaction - |> insert(gas_price: 1) + |> insert(gas_price: 1, block_consensus: false) |> with_block(consensus_block, gas_used: 1) :transaction - |> insert(gas_price: 1) + |> insert(gas_price: 1, block_consensus: false) |> with_block(consensus_block, gas_used: 2) - assert Chain.gas_payment_by_block_hash([non_consensus_block.hash]) == %{} + assert Chain.gas_payment_by_block_hash([non_consensus_block.hash]) == %{ + non_consensus_block.hash => %Wei{value: Decimal.new(0)} + } end test "with consensus block hash without transactions has key with 0 value", %{ @@ -4540,83 +4042,6 @@ defmodule Explorer.ChainTest do end end - describe "stream_not_inserted_token_instances/2" do - test "reduces with given reducer and accumulator for ERC-721 token" do - token_contract_address = insert(:contract_address) - token = insert(:token, contract_address: token_contract_address, type: "ERC-721") - - transaction = - :transaction - |> insert() - |> with_block(insert(:block, number: 1)) - - token_transfer = - insert( - :token_transfer, - block_number: 1000, - to_address: build(:address), - transaction: transaction, - token_contract_address: token_contract_address, - token: token, - token_ids: [11] - ) - - assert {:ok, [result]} = Chain.stream_not_inserted_token_instances([], &[&1 | &2]) - assert result.token_id == List.first(token_transfer.token_ids) - assert result.contract_address_hash == token_transfer.token_contract_address_hash - end - - test "does not fetch token transfers without token_ids" do - token_contract_address = insert(:contract_address) - token = insert(:token, contract_address: token_contract_address, type: "ERC-721") - - transaction = - :transaction - |> insert() - |> with_block(insert(:block, number: 1)) - - insert( - :token_transfer, - block_number: 1000, - to_address: build(:address), - transaction: transaction, - token_contract_address: token_contract_address, - token: token, - token_ids: nil - ) - - assert {:ok, []} = Chain.stream_not_inserted_token_instances([], &[&1 | &2]) - end - - test "do not fetch records with token instances" do - token_contract_address = insert(:contract_address) - token = insert(:token, contract_address: token_contract_address, type: "ERC-721") - - transaction = - :transaction - |> insert() - |> with_block(insert(:block, number: 1)) - - token_transfer = - insert( - :token_transfer, - block_number: 1000, - to_address: build(:address), - transaction: transaction, - token_contract_address: token_contract_address, - token: token, - token_ids: [11] - ) - - insert(:token_instance, - token_id: List.first(token_transfer.token_ids), - token_contract_address_hash: token_transfer.token_contract_address_hash - ) - - assert {:ok, []} = Chain.stream_not_inserted_token_instances([], &[&1 | &2]) - end - end - describe "transaction_has_token_transfers?/1" do test "returns true if transaction has token transfers" do transaction = insert(:transaction) @@ -4949,37 +4374,13 @@ defmodule Explorer.ChainTest do unique_tokens_ids_paginated = token_contract_address.hash - |> Chain.address_to_unique_tokens(paging_options: paging_options) + |> Chain.address_to_unique_tokens(token, paging_options: paging_options) |> Enum.map(& &1.token_id) assert unique_tokens_ids_paginated == [List.first(second_page.token_ids)] end end - describe "uncataloged_token_transfer_block_numbers/0" do - test "returns a list of block numbers" do - block = insert(:block) - address = insert(:address) - - log = - insert(:token_transfer_log, - transaction: - insert(:transaction, - block_number: block.number, - block_hash: block.hash, - cumulative_gas_used: 0, - gas_used: 0, - index: 0 - ), - block: block, - address_hash: address.hash - ) - - block_number = log.block_number - assert {:ok, [^block_number]} = Chain.uncataloged_token_transfer_block_numbers() - end - end - describe "address_to_balances_by_day/1" do test "return a list of balances by day" do address = insert(:address) @@ -5393,64 +4794,4 @@ defmodule Explorer.ChainTest do assert Chain.transaction_to_revert_reason(transaction) == "No credit of that type" end end - - describe "verified_contracts/2" do - test "without contracts" do - assert [] = Chain.verified_contracts() - end - - test "with contracts" do - %SmartContract{address_hash: hash} = insert(:smart_contract) - - assert [%SmartContract{address_hash: ^hash}] = Chain.verified_contracts() - end - - test "with contracts can be paginated" do - second_page_contracts_ids = - 50 - |> insert_list(:smart_contract) - |> Enum.map(& &1.id) - - contract = insert(:smart_contract) - - assert second_page_contracts_ids == - [paging_options: %PagingOptions{key: {contract.id}, page_size: 50}] - |> Chain.verified_contracts() - |> Enum.map(& &1.id) - |> Enum.reverse() - end - - test "filters solidity" do - insert(:smart_contract, is_vyper_contract: true) - %SmartContract{address_hash: hash} = insert(:smart_contract, is_vyper_contract: false) - - assert [%SmartContract{address_hash: ^hash}] = Chain.verified_contracts(filter: :solidity) - end - - test "filters vyper" do - insert(:smart_contract, is_vyper_contract: false) - %SmartContract{address_hash: hash} = insert(:smart_contract, is_vyper_contract: true) - - assert [%SmartContract{address_hash: ^hash}] = Chain.verified_contracts(filter: :vyper) - end - - test "search by address" do - insert(:smart_contract) - insert(:smart_contract) - insert(:smart_contract) - %SmartContract{address_hash: hash} = insert(:smart_contract) - - assert [%SmartContract{address_hash: ^hash}] = Chain.verified_contracts(search: Hash.to_string(hash)) - end - - test "search by name" do - insert(:smart_contract) - insert(:smart_contract) - insert(:smart_contract) - contract_name = "qwertyufhgkhiop" - %SmartContract{address_hash: hash} = insert(:smart_contract, name: contract_name) - - assert [%SmartContract{address_hash: ^hash}] = Chain.verified_contracts(search: contract_name) - end - end end diff --git a/apps/explorer/test/explorer/etherscan/logs_test.exs b/apps/explorer/test/explorer/etherscan/logs_test.exs index 490dce199dc6..b5953407f83c 100644 --- a/apps/explorer/test/explorer/etherscan/logs_test.exs +++ b/apps/explorer/test/explorer/etherscan/logs_test.exs @@ -6,6 +6,25 @@ defmodule Explorer.Etherscan.LogsTest do alias Explorer.Etherscan.Logs alias Explorer.Chain.Transaction + @first_topic_hex_string_1 "0x7fcf532c15f0a6db0bd6d0e038bea71d30d808c7d98cb3bf7268a95bf5081b65" + @first_topic_hex_string_2 "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef" + @first_topic_hex_string_3 "0xe1fffcc4923d04b559f4d29a8bfc6cda04eb5b0d3c460751c2402c5c5cc9109c" + + @second_topic_hex_string_1 "0x00000000000000000000000098a9dc37d3650b5b30d6c12789b3881ee0b70c16" + @second_topic_hex_string_2 "0x000000000000000000000000e2680fd7cdbb04e9087a647ad4d023ef6c8fb4e2" + @second_topic_hex_string_3 "0x0000000000000000000000005777d92f208679db4b9778590fa3cab3ac9e2168" + + @third_topic_hex_string_1 "0x0000000000000000000000005079fc00f00f30000e0c8c083801cfde000008b6" + @third_topic_hex_string_2 "0x000000000000000000000000e2680fd7cdbb04e9087a647ad4d023ef6c8fb4e2" + @third_topic_hex_string_3 "0x0000000000000000000000000f6d9bd6fc315bbf95b5c44f4eba2b2762f8c372" + + @fourth_topic_hex_string_1 "0x8c9b7729443a4444242342b2ca385a239a5c1d76a88473e1cd2ab0c70dd1b9c7" + + defp topic(topic_hex_string) do + {:ok, topic} = Explorer.Chain.Hash.Full.cast(topic_hex_string) + topic + end + describe "list_logs/1" do test "with empty db" do contract_address = build(:contract_address) @@ -38,14 +57,15 @@ defmodule Explorer.Etherscan.LogsTest do test "with address with one log response includes all required information" do contract_address = insert(:contract_address) + block = insert(:block) transaction = - %Transaction{block: block} = + %Transaction{} = :transaction - |> insert(to_address: contract_address) - |> with_block() + |> insert(to_address: contract_address, block_timestamp: block.timestamp) + |> with_block(block) - log = insert(:log, address: contract_address, transaction: transaction) + log = insert(:log, address: contract_address, block_number: block.number, transaction: transaction) filter = %{ from_block: block.number, @@ -79,7 +99,7 @@ defmodule Explorer.Etherscan.LogsTest do |> insert(to_address: contract_address) |> with_block() - insert_list(2, :log, address: contract_address, transaction: transaction) + insert_list(2, :log, address: contract_address, transaction: transaction, block_number: block.number) filter = %{ from_block: block.number, @@ -110,8 +130,8 @@ defmodule Explorer.Etherscan.LogsTest do |> insert(to_address: contract_address) |> with_block(second_block) - insert(:log, address: contract_address, transaction: transaction_block1) - insert(:log, address: contract_address, transaction: transaction_block2) + insert(:log, address: contract_address, transaction: transaction_block1, block_number: first_block.number) + insert(:log, address: contract_address, transaction: transaction_block2, block_number: second_block.number) filter = %{ from_block: second_block.number, @@ -143,8 +163,8 @@ defmodule Explorer.Etherscan.LogsTest do |> insert(to_address: contract_address) |> with_block(second_block) - insert(:log, address: contract_address, transaction: transaction_block1) - insert(:log, address: contract_address, transaction: transaction_block2) + insert(:log, address: contract_address, transaction: transaction_block1, block_number: first_block.number) + insert(:log, address: contract_address, transaction: transaction_block2, block_number: second_block.number) filter = %{ from_block: first_block.number, @@ -167,7 +187,8 @@ defmodule Explorer.Etherscan.LogsTest do |> insert(to_address: contract_address) |> with_block() - inserted_records = insert_list(2000, :log, address: contract_address, transaction: transaction) + inserted_records = + insert_list(2000, :log, address: contract_address, transaction: transaction, block_number: block.number) filter = %{ from_block: block.number, @@ -183,7 +204,6 @@ defmodule Explorer.Etherscan.LogsTest do next_page_params = %{ log_index: last_record.index, - transaction_index: last_record.transaction_index, block_number: transaction.block_number } @@ -210,13 +230,13 @@ defmodule Explorer.Etherscan.LogsTest do log1_details = [ address: contract_address, transaction: transaction, - first_topic: "some topic" + first_topic: topic(@first_topic_hex_string_1) ] log2_details = [ address: contract_address, transaction: transaction, - first_topic: "some other topic" + first_topic: topic(@first_topic_hex_string_2) ] log1 = insert(:log, log1_details) @@ -246,15 +266,15 @@ defmodule Explorer.Etherscan.LogsTest do log1_details = [ address: contract_address, transaction: transaction, - first_topic: "some first topic", - second_topic: "some second topic" + first_topic: topic(@first_topic_hex_string_1), + second_topic: topic(@second_topic_hex_string_1) ] log2_details = [ address: contract_address, transaction: transaction, - first_topic: "some first topic", - second_topic: "some OTHER second topic" + first_topic: topic(@first_topic_hex_string_1), + second_topic: topic(@second_topic_hex_string_2) ] _log1 = insert(:log, log1_details) @@ -287,15 +307,15 @@ defmodule Explorer.Etherscan.LogsTest do log1_details = [ address: contract_address, transaction: transaction, - first_topic: "some first topic", - second_topic: "some second topic" + first_topic: topic(@first_topic_hex_string_1), + second_topic: topic(@second_topic_hex_string_1) ] log2_details = [ address: contract_address, transaction: transaction, - first_topic: "some OTHER first topic", - second_topic: "some OTHER second topic" + first_topic: topic(@first_topic_hex_string_2), + second_topic: topic(@second_topic_hex_string_2) ] log1 = insert(:log, log1_details) @@ -326,13 +346,15 @@ defmodule Explorer.Etherscan.LogsTest do log1_details = [ address: contract_address, transaction: transaction, - first_topic: "some first topic" + first_topic: topic(@first_topic_hex_string_1), + block_number: block.number ] log2_details = [ address: contract_address, transaction: transaction, - first_topic: "some OTHER first topic" + first_topic: topic(@first_topic_hex_string_2), + block_number: block.number ] _log1 = insert(:log, log1_details) @@ -363,15 +385,17 @@ defmodule Explorer.Etherscan.LogsTest do log1_details = [ address: contract_address, transaction: transaction, - first_topic: "some first topic", - second_topic: "some second topic" + first_topic: topic(@first_topic_hex_string_1), + second_topic: topic(@second_topic_hex_string_1), + block_number: block.number ] log2_details = [ address: contract_address, transaction: transaction, - first_topic: "some OTHER first topic", - second_topic: "some OTHER second topic" + first_topic: topic(@first_topic_hex_string_2), + second_topic: topic(@second_topic_hex_string_2), + block_number: block.number ] _log1 = insert(:log, log1_details) @@ -404,25 +428,28 @@ defmodule Explorer.Etherscan.LogsTest do log1_details = [ address: contract_address, transaction: transaction, - first_topic: "some first topic", - second_topic: "some second topic", - third_topic: "some third topic" + first_topic: topic(@first_topic_hex_string_1), + second_topic: topic(@second_topic_hex_string_1), + third_topic: topic(@third_topic_hex_string_1), + block_number: block.number ] log2_details = [ address: contract_address, transaction: transaction, - first_topic: "some OTHER first topic", - second_topic: "some OTHER second topic", - third_topic: "some OTHER third topic" + first_topic: topic(@first_topic_hex_string_2), + second_topic: topic(@second_topic_hex_string_2), + third_topic: topic(@third_topic_hex_string_2), + block_number: block.number ] log3_details = [ address: contract_address, transaction: transaction, - first_topic: "some ALT first topic", - second_topic: "some ALT second topic", - third_topic: "some ALT third topic" + first_topic: topic(@first_topic_hex_string_3), + second_topic: topic(@second_topic_hex_string_3), + third_topic: topic(@third_topic_hex_string_3), + block_number: block.number ] _log1 = insert(:log, log1_details) @@ -461,25 +488,28 @@ defmodule Explorer.Etherscan.LogsTest do log1_details = [ address: contract_address, transaction: transaction, - first_topic: "some first topic", - second_topic: "some second topic", - third_topic: "some third topic" + first_topic: topic(@first_topic_hex_string_1), + second_topic: topic(@second_topic_hex_string_1), + third_topic: topic(@third_topic_hex_string_1), + block_number: block.number ] log2_details = [ address: contract_address, transaction: transaction, - first_topic: "some OTHER first topic", - second_topic: "some OTHER second topic", - third_topic: "some OTHER third topic" + first_topic: topic(@first_topic_hex_string_2), + second_topic: topic(@second_topic_hex_string_2), + third_topic: topic(@third_topic_hex_string_2), + block_number: block.number ] log3_details = [ address: contract_address, transaction: transaction, - first_topic: "some ALT first topic", - second_topic: "some ALT second topic", - third_topic: "some ALT third topic" + first_topic: topic(@first_topic_hex_string_3), + second_topic: topic(@second_topic_hex_string_3), + third_topic: topic(@third_topic_hex_string_3), + block_number: block.number ] log1 = insert(:log, log1_details) @@ -518,25 +548,28 @@ defmodule Explorer.Etherscan.LogsTest do log1_details = [ address: contract_address, transaction: transaction, - first_topic: "some topic", - second_topic: "some second topic", - third_topic: "some third topic" + first_topic: topic(@first_topic_hex_string_1), + second_topic: topic(@second_topic_hex_string_1), + third_topic: topic(@third_topic_hex_string_1), + block_number: block.number ] log2_details = [ address: contract_address, transaction: transaction, - first_topic: "some topic", - second_topic: "some OTHER second topic", - third_topic: "some third topic" + first_topic: topic(@first_topic_hex_string_1), + second_topic: topic(@second_topic_hex_string_2), + third_topic: topic(@third_topic_hex_string_1), + block_number: block.number ] log3_details = [ address: contract_address, transaction: transaction, - first_topic: "some topic", - second_topic: "some second topic", - third_topic: "some third topic" + first_topic: topic(@first_topic_hex_string_1), + second_topic: topic(@second_topic_hex_string_1), + third_topic: topic(@third_topic_hex_string_1), + block_number: block.number ] log1 = insert(:log, log1_details) @@ -575,26 +608,29 @@ defmodule Explorer.Etherscan.LogsTest do log1_details = [ address: contract_address, transaction: transaction, - first_topic: "some topic", - second_topic: "some second topic" + first_topic: topic(@first_topic_hex_string_1), + second_topic: topic(@second_topic_hex_string_1), + block_number: block.number ] log2_details = [ address: contract_address, transaction: transaction, - first_topic: "some OTHER topic", - second_topic: "some OTHER second topic", - third_topic: "some OTHER third topic", - fourth_topic: "some fourth topic" + first_topic: topic(@first_topic_hex_string_2), + second_topic: topic(@second_topic_hex_string_2), + third_topic: topic(@third_topic_hex_string_2), + fourth_topic: topic(@fourth_topic_hex_string_1), + block_number: block.number ] log3_details = [ address: contract_address, transaction: transaction, - first_topic: "some topic", - second_topic: "some second topic", - third_topic: "some third topic", - fourth_topic: "some fourth topic" + first_topic: topic(@first_topic_hex_string_1), + second_topic: topic(@second_topic_hex_string_1), + third_topic: topic(@third_topic_hex_string_1), + fourth_topic: topic(@fourth_topic_hex_string_1), + block_number: block.number ] log1 = insert(:log, log1_details) diff --git a/apps/explorer/test/explorer/etherscan_test.exs b/apps/explorer/test/explorer/etherscan_test.exs index 02cc0ced53cb..7d188cd98f49 100644 --- a/apps/explorer/test/explorer/etherscan_test.exs +++ b/apps/explorer/test/explorer/etherscan_test.exs @@ -159,11 +159,12 @@ defmodule Explorer.EtherscanTest do test "loads block_timestamp" do address = insert(:address) + block = insert(:block) - %Transaction{block: block} = + %Transaction{} = :transaction - |> insert(from_address: address) - |> with_block() + |> insert(from_address: address, block_timestamp: block.timestamp) + |> with_block(block) [found_transaction] = Etherscan.list_transactions(address.hash) @@ -370,7 +371,7 @@ defmodule Explorer.EtherscanTest do for block <- Enum.concat([blocks1, blocks2, blocks3]) do 2 - |> insert_list(:transaction, from_address: address) + |> insert_list(:transaction, from_address: address, block_timestamp: block.timestamp) |> with_block(block) end @@ -629,7 +630,7 @@ defmodule Explorer.EtherscanTest do transaction = :transaction - |> insert(from_address: address, to_address: nil) + |> insert(from_address: address, to_address: nil, block_timestamp: block.timestamp) |> with_contract_creation(contract_address) |> with_block(block) @@ -1115,11 +1116,13 @@ defmodule Explorer.EtherscanTest do end test "returns all required fields" do + block = insert(:block) + transaction = %{block: block} = :transaction - |> insert() - |> with_block() + |> insert(block_timestamp: block.timestamp) + |> with_block(block) token_transfer = insert(:token_transfer, diff --git a/apps/explorer/test/explorer/migrator/address_current_token_balance_token_type_test.exs b/apps/explorer/test/explorer/migrator/address_current_token_balance_token_type_test.exs new file mode 100644 index 000000000000..27591d9c52c1 --- /dev/null +++ b/apps/explorer/test/explorer/migrator/address_current_token_balance_token_type_test.exs @@ -0,0 +1,33 @@ +defmodule Explorer.Migrator.AddressCurrentTokenBalanceTokenTypeTest do + use Explorer.DataCase, async: false + + alias Explorer.Chain.Cache.BackgroundMigrations + alias Explorer.Chain.Address.CurrentTokenBalance + alias Explorer.Migrator.{AddressCurrentTokenBalanceTokenType, MigrationStatus} + alias Explorer.Repo + + describe "Migrate current token balances" do + test "Set token_type for not processed current token balances" do + Enum.each(0..10, fn _x -> + current_token_balance = insert(:address_current_token_balance, token_type: nil) + assert %{token_type: nil} = current_token_balance + end) + + assert MigrationStatus.get_status("ctb_token_type") == nil + + AddressCurrentTokenBalanceTokenType.start_link([]) + Process.sleep(100) + + CurrentTokenBalance + |> Repo.all() + |> Repo.preload(:token) + |> Enum.each(fn ctb -> + assert %{token_type: token_type, token: %{type: token_type}} = ctb + assert not is_nil(token_type) + end) + + assert MigrationStatus.get_status("ctb_token_type") == "completed" + assert BackgroundMigrations.get_ctb_token_type_finished() == true + end + end +end diff --git a/apps/explorer/test/explorer/migrator/address_token_balance_token_type_test.exs b/apps/explorer/test/explorer/migrator/address_token_balance_token_type_test.exs new file mode 100644 index 000000000000..4bf09c57122c --- /dev/null +++ b/apps/explorer/test/explorer/migrator/address_token_balance_token_type_test.exs @@ -0,0 +1,33 @@ +defmodule Explorer.Migrator.AddressTokenBalanceTokenTypeTest do + use Explorer.DataCase, async: false + + alias Explorer.Chain.Cache.BackgroundMigrations + alias Explorer.Chain.Address.TokenBalance + alias Explorer.Migrator.{AddressTokenBalanceTokenType, MigrationStatus} + alias Explorer.Repo + + describe "Migrate token balances" do + test "Set token_type for not processed token balances" do + Enum.each(0..10, fn _x -> + token_balance = insert(:token_balance, token_type: nil) + assert %{token_type: nil} = token_balance + end) + + assert MigrationStatus.get_status("tb_token_type") == nil + + AddressTokenBalanceTokenType.start_link([]) + Process.sleep(100) + + TokenBalance + |> Repo.all() + |> Repo.preload(:token) + |> Enum.each(fn tb -> + assert %{token_type: token_type, token: %{type: token_type}} = tb + assert not is_nil(token_type) + end) + + assert MigrationStatus.get_status("tb_token_type") == "completed" + assert BackgroundMigrations.get_tb_token_type_finished() == true + end + end +end diff --git a/apps/explorer/test/explorer/migrator/transactions_denormalization_migrator_test.exs b/apps/explorer/test/explorer/migrator/transactions_denormalization_migrator_test.exs new file mode 100644 index 000000000000..e47afe96da4d --- /dev/null +++ b/apps/explorer/test/explorer/migrator/transactions_denormalization_migrator_test.exs @@ -0,0 +1,43 @@ +defmodule Explorer.Migrator.TransactionsDenormalizationTest do + use Explorer.DataCase, async: false + + alias Explorer.Chain.Cache.BackgroundMigrations + alias Explorer.Chain.Transaction + alias Explorer.Migrator.{MigrationStatus, TransactionsDenormalization} + alias Explorer.Repo + + describe "Migrate transactions" do + test "Set block_consensus and block_timestamp for not processed transactions" do + Enum.each(0..10, fn _x -> + transaction = + :transaction + |> insert() + |> with_block(block_timestamp: nil, block_consensus: nil) + + assert %{block_consensus: nil, block_timestamp: nil, block: %{consensus: consensus, timestamp: timestamp}} = + transaction + + assert not is_nil(consensus) + assert not is_nil(timestamp) + end) + + assert MigrationStatus.get_status("denormalization") == nil + + TransactionsDenormalization.start_link([]) + Process.sleep(100) + + Transaction + |> Repo.all() + |> Repo.preload(:block) + |> Enum.each(fn t -> + assert %{ + block_consensus: consensus, + block_timestamp: timestamp, + block: %{consensus: consensus, timestamp: timestamp} + } = t + end) + + assert MigrationStatus.get_status("denormalization") == "completed" + end + end +end diff --git a/apps/explorer/test/explorer/token_transfer_token_id_migration/lowest_block_number_updater_test.exs b/apps/explorer/test/explorer/token_transfer_token_id_migration/lowest_block_number_updater_test.exs deleted file mode 100644 index bdd94920db2c..000000000000 --- a/apps/explorer/test/explorer/token_transfer_token_id_migration/lowest_block_number_updater_test.exs +++ /dev/null @@ -1,37 +0,0 @@ -defmodule Explorer.TokenTransferTokenIdMigration.LowestBlockNumberUpdaterTest do - use Explorer.DataCase, async: false - - alias Explorer.Repo - alias Explorer.TokenTransferTokenIdMigration.LowestBlockNumberUpdater - alias Explorer.Utility.TokenTransferTokenIdMigratorProgress - - describe "Add range and update last processed block number" do - test "add_range/2" do - TokenTransferTokenIdMigratorProgress.update_last_processed_block_number(2000, true) - LowestBlockNumberUpdater.start_link([]) - - LowestBlockNumberUpdater.add_range(1000, 500) - LowestBlockNumberUpdater.add_range(1500, 1001) - Process.sleep(10) - - assert %{last_processed_block_number: 2000, processed_ranges: [1500..500//-1]} = - :sys.get_state(LowestBlockNumberUpdater) - - assert %{last_processed_block_number: 2000} = Repo.one(TokenTransferTokenIdMigratorProgress) - - LowestBlockNumberUpdater.add_range(499, 300) - LowestBlockNumberUpdater.add_range(299, 0) - Process.sleep(10) - - assert %{last_processed_block_number: 2000, processed_ranges: [1500..0//-1]} = - :sys.get_state(LowestBlockNumberUpdater) - - assert %{last_processed_block_number: 2000} = Repo.one(TokenTransferTokenIdMigratorProgress) - - LowestBlockNumberUpdater.add_range(1999, 1501) - Process.sleep(10) - assert %{last_processed_block_number: 0, processed_ranges: []} = :sys.get_state(LowestBlockNumberUpdater) - assert %{last_processed_block_number: 0} = Repo.one(TokenTransferTokenIdMigratorProgress) - end - end -end diff --git a/apps/explorer/test/explorer/token_transfer_token_id_migration/worker_test.exs b/apps/explorer/test/explorer/token_transfer_token_id_migration/worker_test.exs deleted file mode 100644 index 8797e90130e6..000000000000 --- a/apps/explorer/test/explorer/token_transfer_token_id_migration/worker_test.exs +++ /dev/null @@ -1,31 +0,0 @@ -defmodule Explorer.TokenTransferTokenIdMigration.WorkerTest do - use Explorer.DataCase, async: false - - alias Explorer.Repo - alias Explorer.TokenTransferTokenIdMigration.{LowestBlockNumberUpdater, Worker} - alias Explorer.Utility.TokenTransferTokenIdMigratorProgress - - describe "Move TokenTransfer token_id to token_ids" do - test "Move token_ids and update last processed block number" do - insert(:token_transfer, block_number: 1, token_id: 1, transaction: insert(:transaction)) - insert(:token_transfer, block_number: 500, token_id: 2, transaction: insert(:transaction)) - insert(:token_transfer, block_number: 1000, token_id: 3, transaction: insert(:transaction)) - insert(:token_transfer, block_number: 1500, token_id: 4, transaction: insert(:transaction)) - insert(:token_transfer, block_number: 2000, token_id: 5, transaction: insert(:transaction)) - - TokenTransferTokenIdMigratorProgress.update_last_processed_block_number(3000, true) - LowestBlockNumberUpdater.start_link([]) - - Worker.start_link(idx: 1, first_block: 0, last_block: 3000, step: 2) - Worker.start_link(idx: 2, first_block: 0, last_block: 3000, step: 2) - Worker.start_link(idx: 3, first_block: 0, last_block: 3000, step: 2) - Process.sleep(200) - - token_transfers = Repo.all(Explorer.Chain.TokenTransfer) - assert Enum.all?(token_transfers, fn tt -> is_nil(tt.token_id) end) - - expected_token_ids = [[Decimal.new(1)], [Decimal.new(2)], [Decimal.new(3)], [Decimal.new(4)], [Decimal.new(5)]] - assert ^expected_token_ids = token_transfers |> Enum.map(& &1.token_ids) |> Enum.sort_by(&List.first/1) - end - end -end diff --git a/apps/explorer/test/support/factory.ex b/apps/explorer/test/support/factory.ex index 89712c83e82e..5fdd286ee056 100644 --- a/apps/explorer/test/support/factory.ex +++ b/apps/explorer/test/support/factory.ex @@ -533,7 +533,7 @@ defmodule Explorer.Factory do %Transaction{index: nil} = transaction, # The `transaction.block` must be consensus. Non-consensus blocks can only be associated with the # `transaction_forks`. - %Block{consensus: true, hash: block_hash, number: block_number}, + %Block{consensus: true, hash: block_hash, number: block_number, timestamp: timestamp}, collated_params ) when is_list(collated_params) do @@ -542,6 +542,8 @@ defmodule Explorer.Factory do cumulative_gas_used = collated_params[:cumulative_gas_used] || Enum.random(21_000..100_000) gas_used = collated_params[:gas_used] || Enum.random(21_000..100_000) status = Keyword.get(collated_params, :status, Enum.random([:ok, :error])) + block_timestamp = Keyword.get(collated_params, :block_timestamp, timestamp) + block_consensus = Keyword.get(collated_params, :block_consensus, true) error = (status == :error && collated_params[:error]) || nil @@ -555,7 +557,9 @@ defmodule Explorer.Factory do error: error, gas_used: gas_used, index: next_transaction_index, - status: status + status: status, + block_timestamp: block_timestamp, + block_consensus: block_consensus }) |> Repo.update!() |> Repo.preload(:block) @@ -675,8 +679,7 @@ defmodule Explorer.Factory do index: sequence("log_index", & &1), second_topic: nil, third_topic: nil, - transaction: build(:transaction), - type: sequence("0x") + transaction: build(:transaction) } end @@ -807,7 +810,8 @@ defmodule Explorer.Factory do s: sequence(:transaction_s, & &1), to_address: build(:address), v: Enum.random(27..30), - value: Enum.random(1..100_000) + value: Enum.random(1..100_000), + block_timestamp: DateTime.utc_now() } end diff --git a/apps/indexer/config/dev/arbitrum.exs b/apps/indexer/config/dev/arbitrum.exs index 18125586af44..e708fed5441b 100644 --- a/apps/indexer/config/dev/arbitrum.exs +++ b/apps/indexer/config/dev/arbitrum.exs @@ -19,6 +19,9 @@ config :indexer, http: EthereumJSONRPC.HTTP.HTTPoison, url: System.get_env("ETHEREUM_JSONRPC_HTTP_URL") || "http://localhost:8545", fallback_url: System.get_env("ETHEREUM_JSONRPC_FALLBACK_HTTP_URL"), + method_to_url: [ + eth_call: ConfigHelper.eth_call_url("http://localhost:8545") + ], http_options: [recv_timeout: timeout, timeout: timeout, hackney: hackney_opts] ], variant: EthereumJSONRPC.Arbitrum diff --git a/apps/indexer/config/dev/besu.exs b/apps/indexer/config/dev/besu.exs index c6d8d4a1aa00..e09c82159481 100644 --- a/apps/indexer/config/dev/besu.exs +++ b/apps/indexer/config/dev/besu.exs @@ -22,6 +22,7 @@ config :indexer, fallback_url: System.get_env("ETHEREUM_JSONRPC_FALLBACK_HTTP_URL"), fallback_trace_url: System.get_env("ETHEREUM_JSONRPC_FALLBACK_TRACE_URL"), method_to_url: [ + eth_call: ConfigHelper.eth_call_url("http://localhost:8545"), eth_getBalance: System.get_env("ETHEREUM_JSONRPC_TRACE_URL") || "http://localhost:8545", trace_block: System.get_env("ETHEREUM_JSONRPC_TRACE_URL") || "http://localhost:8545", trace_replayBlockTransactions: System.get_env("ETHEREUM_JSONRPC_TRACE_URL") || "http://localhost:8545", diff --git a/apps/indexer/config/dev/erigon.exs b/apps/indexer/config/dev/erigon.exs index 912b64dde2ec..ef77231c83e5 100644 --- a/apps/indexer/config/dev/erigon.exs +++ b/apps/indexer/config/dev/erigon.exs @@ -22,6 +22,7 @@ config :indexer, fallback_url: System.get_env("ETHEREUM_JSONRPC_FALLBACK_HTTP_URL"), fallback_trace_url: System.get_env("ETHEREUM_JSONRPC_FALLBACK_TRACE_URL"), method_to_url: [ + eth_call: ConfigHelper.eth_call_url("http://localhost:8545"), eth_getBalance: System.get_env("ETHEREUM_JSONRPC_TRACE_URL") || "http://localhost:8545", trace_block: System.get_env("ETHEREUM_JSONRPC_TRACE_URL") || "http://localhost:8545", trace_replayBlockTransactions: System.get_env("ETHEREUM_JSONRPC_TRACE_URL") || "http://localhost:8545", diff --git a/apps/indexer/config/dev/ganache.exs b/apps/indexer/config/dev/ganache.exs index be2cd745b191..95ed0de84d89 100644 --- a/apps/indexer/config/dev/ganache.exs +++ b/apps/indexer/config/dev/ganache.exs @@ -19,6 +19,9 @@ config :indexer, http: EthereumJSONRPC.HTTP.HTTPoison, url: System.get_env("ETHEREUM_JSONRPC_HTTP_URL") || "http://localhost:7545", fallback_url: System.get_env("ETHEREUM_JSONRPC_FALLBACK_HTTP_URL"), + method_to_url: [ + eth_call: ConfigHelper.eth_call_url("http://localhost:7545") + ], http_options: [recv_timeout: timeout, timeout: timeout, hackney: hackney_opts] ], variant: EthereumJSONRPC.Ganache diff --git a/apps/indexer/config/dev/geth.exs b/apps/indexer/config/dev/geth.exs index 87b9dbe90613..b7eb4d7facf0 100644 --- a/apps/indexer/config/dev/geth.exs +++ b/apps/indexer/config/dev/geth.exs @@ -21,6 +21,7 @@ config :indexer, fallback_url: System.get_env("ETHEREUM_JSONRPC_FALLBACK_HTTP_URL"), fallback_trace_url: System.get_env("ETHEREUM_JSONRPC_FALLBACK_TRACE_URL"), method_to_url: [ + eth_call: ConfigHelper.eth_call_url("http://localhost:8545"), debug_traceTransaction: System.get_env("ETHEREUM_JSONRPC_TRACE_URL") || "http://localhost:8545" ], http_options: [recv_timeout: timeout, timeout: timeout, hackney: hackney_opts] diff --git a/apps/indexer/config/dev/nethermind.exs b/apps/indexer/config/dev/nethermind.exs index a00a7acd1781..d97935eb14da 100644 --- a/apps/indexer/config/dev/nethermind.exs +++ b/apps/indexer/config/dev/nethermind.exs @@ -22,6 +22,7 @@ config :indexer, fallback_url: System.get_env("ETHEREUM_JSONRPC_FALLBACK_HTTP_URL"), fallback_trace_url: System.get_env("ETHEREUM_JSONRPC_FALLBACK_TRACE_URL"), method_to_url: [ + eth_call: ConfigHelper.eth_call_url("http://localhost:8545"), eth_getBalance: System.get_env("ETHEREUM_JSONRPC_TRACE_URL") || "http://localhost:8545", trace_block: System.get_env("ETHEREUM_JSONRPC_TRACE_URL") || "http://localhost:8545", trace_replayBlockTransactions: System.get_env("ETHEREUM_JSONRPC_TRACE_URL") || "http://localhost:8545", diff --git a/apps/indexer/config/dev/rsk.exs b/apps/indexer/config/dev/rsk.exs index 5168fdbbff10..b609f78a224c 100644 --- a/apps/indexer/config/dev/rsk.exs +++ b/apps/indexer/config/dev/rsk.exs @@ -23,6 +23,7 @@ config :indexer, fallback_url: System.get_env("ETHEREUM_JSONRPC_FALLBACK_HTTP_URL"), fallback_trace_url: System.get_env("ETHEREUM_JSONRPC_FALLBACK_TRACE_URL"), method_to_url: [ + eth_call: ConfigHelper.eth_call_url("http://localhost:8545"), eth_getBalance: System.get_env("ETHEREUM_JSONRPC_TRACE_URL") || "http://localhost:8545", trace_block: System.get_env("ETHEREUM_JSONRPC_TRACE_URL") || "http://localhost:8545", trace_replayBlockTransactions: System.get_env("ETHEREUM_JSONRPC_TRACE_URL") || "http://localhost:8545", diff --git a/apps/indexer/config/prod/arbitrum.exs b/apps/indexer/config/prod/arbitrum.exs index cef49883b46e..6cf72e206a26 100644 --- a/apps/indexer/config/prod/arbitrum.exs +++ b/apps/indexer/config/prod/arbitrum.exs @@ -19,6 +19,9 @@ config :indexer, http: EthereumJSONRPC.HTTP.HTTPoison, url: System.get_env("ETHEREUM_JSONRPC_HTTP_URL") || "http://localhost:8545", fallback_url: System.get_env("ETHEREUM_JSONRPC_FALLBACK_HTTP_URL"), + method_to_url: [ + eth_call: ConfigHelper.eth_call_url("http://localhost:8545") + ], http_options: [recv_timeout: timeout, timeout: timeout, hackney: hackney_opts] ], variant: EthereumJSONRPC.Arbitrum diff --git a/apps/indexer/config/prod/besu.exs b/apps/indexer/config/prod/besu.exs index 7970b1207799..576bdcef3bcf 100644 --- a/apps/indexer/config/prod/besu.exs +++ b/apps/indexer/config/prod/besu.exs @@ -21,6 +21,7 @@ config :indexer, fallback_url: System.get_env("ETHEREUM_JSONRPC_FALLBACK_HTTP_URL"), fallback_trace_url: System.get_env("ETHEREUM_JSONRPC_FALLBACK_TRACE_URL"), method_to_url: [ + eth_call: ConfigHelper.eth_call_url(), eth_getBalance: System.get_env("ETHEREUM_JSONRPC_TRACE_URL"), trace_block: System.get_env("ETHEREUM_JSONRPC_TRACE_URL"), trace_replayBlockTransactions: System.get_env("ETHEREUM_JSONRPC_TRACE_URL"), diff --git a/apps/indexer/config/prod/erigon.exs b/apps/indexer/config/prod/erigon.exs index 27b2c410a96c..84a5c6250390 100644 --- a/apps/indexer/config/prod/erigon.exs +++ b/apps/indexer/config/prod/erigon.exs @@ -21,6 +21,7 @@ config :indexer, fallback_url: System.get_env("ETHEREUM_JSONRPC_FALLBACK_HTTP_URL"), fallback_trace_url: System.get_env("ETHEREUM_JSONRPC_FALLBACK_TRACE_URL"), method_to_url: [ + eth_call: ConfigHelper.eth_call_url(), eth_getBalance: System.get_env("ETHEREUM_JSONRPC_TRACE_URL"), trace_block: System.get_env("ETHEREUM_JSONRPC_TRACE_URL"), trace_replayBlockTransactions: System.get_env("ETHEREUM_JSONRPC_TRACE_URL"), diff --git a/apps/indexer/config/prod/ganache.exs b/apps/indexer/config/prod/ganache.exs index be2cd745b191..95ed0de84d89 100644 --- a/apps/indexer/config/prod/ganache.exs +++ b/apps/indexer/config/prod/ganache.exs @@ -19,6 +19,9 @@ config :indexer, http: EthereumJSONRPC.HTTP.HTTPoison, url: System.get_env("ETHEREUM_JSONRPC_HTTP_URL") || "http://localhost:7545", fallback_url: System.get_env("ETHEREUM_JSONRPC_FALLBACK_HTTP_URL"), + method_to_url: [ + eth_call: ConfigHelper.eth_call_url("http://localhost:7545") + ], http_options: [recv_timeout: timeout, timeout: timeout, hackney: hackney_opts] ], variant: EthereumJSONRPC.Ganache diff --git a/apps/indexer/config/prod/geth.exs b/apps/indexer/config/prod/geth.exs index f0b57f9b902f..59bb2c23e1f4 100644 --- a/apps/indexer/config/prod/geth.exs +++ b/apps/indexer/config/prod/geth.exs @@ -21,6 +21,7 @@ config :indexer, fallback_url: System.get_env("ETHEREUM_JSONRPC_FALLBACK_HTTP_URL"), fallback_trace_url: System.get_env("ETHEREUM_JSONRPC_FALLBACK_TRACE_URL"), method_to_url: [ + eth_call: ConfigHelper.eth_call_url(), debug_traceTransaction: System.get_env("ETHEREUM_JSONRPC_TRACE_URL") ], http_options: [recv_timeout: timeout, timeout: timeout, hackney: hackney_opts] diff --git a/apps/indexer/config/prod/nethermind.exs b/apps/indexer/config/prod/nethermind.exs index baed67a6913d..2dc6c0f98e80 100644 --- a/apps/indexer/config/prod/nethermind.exs +++ b/apps/indexer/config/prod/nethermind.exs @@ -21,6 +21,7 @@ config :indexer, fallback_url: System.get_env("ETHEREUM_JSONRPC_FALLBACK_HTTP_URL"), fallback_trace_url: System.get_env("ETHEREUM_JSONRPC_FALLBACK_TRACE_URL"), method_to_url: [ + eth_call: ConfigHelper.eth_call_url(), eth_getBalance: System.get_env("ETHEREUM_JSONRPC_TRACE_URL"), trace_block: System.get_env("ETHEREUM_JSONRPC_TRACE_URL"), trace_replayBlockTransactions: System.get_env("ETHEREUM_JSONRPC_TRACE_URL"), diff --git a/apps/indexer/config/prod/rsk.exs b/apps/indexer/config/prod/rsk.exs index f9090ddd3760..444eff26e653 100644 --- a/apps/indexer/config/prod/rsk.exs +++ b/apps/indexer/config/prod/rsk.exs @@ -23,6 +23,7 @@ config :indexer, fallback_url: System.get_env("ETHEREUM_JSONRPC_FALLBACK_HTTP_URL"), fallback_trace_url: System.get_env("ETHEREUM_JSONRPC_FALLBACK_TRACE_URL"), method_to_url: [ + eth_call: ConfigHelper.eth_call_url(), eth_getBalance: System.get_env("ETHEREUM_JSONRPC_TRACE_URL"), trace_block: System.get_env("ETHEREUM_JSONRPC_TRACE_URL"), trace_replayBlockTransactions: System.get_env("ETHEREUM_JSONRPC_TRACE_URL"), diff --git a/apps/indexer/lib/indexer/block/catchup/helper.ex b/apps/indexer/lib/indexer/block/catchup/helper.ex deleted file mode 100644 index 951ed3548a46..000000000000 --- a/apps/indexer/lib/indexer/block/catchup/helper.ex +++ /dev/null @@ -1,39 +0,0 @@ -defmodule Indexer.Block.Catchup.Helper do - @moduledoc """ - Catchup helper functions - """ - - def sanitize_ranges(ranges) do - ranges - |> Enum.filter(&(not is_nil(&1))) - |> Enum.sort_by( - fn - from.._to -> from - el -> el - end, - :asc - ) - |> Enum.chunk_while( - nil, - fn - _from.._to = chunk, nil -> - {:cont, chunk} - - _ch_from..ch_to = chunk, acc_from..acc_to = acc -> - if Range.disjoint?(chunk, acc), - do: {:cont, acc, chunk}, - else: {:cont, acc_from..max(ch_to, acc_to)} - - num, nil -> - {:halt, num} - - num, acc_from.._ = acc -> - if Range.disjoint?(num..num, acc), do: {:cont, acc, num}, else: {:halt, acc_from} - - _, num -> - {:halt, num} - end, - fn reminder -> {:cont, reminder, nil} end - ) - end -end diff --git a/apps/indexer/lib/indexer/block/catchup/missing_ranges_collector.ex b/apps/indexer/lib/indexer/block/catchup/missing_ranges_collector.ex index 31d067217d8d..9c776610ae81 100644 --- a/apps/indexer/lib/indexer/block/catchup/missing_ranges_collector.ex +++ b/apps/indexer/lib/indexer/block/catchup/missing_ranges_collector.ex @@ -5,11 +5,10 @@ defmodule Indexer.Block.Catchup.MissingRangesCollector do use GenServer - alias Explorer.{Chain, Repo} + alias EthereumJSONRPC.Utility.RangesHelper + alias Explorer.{Chain, Helper, Repo} alias Explorer.Chain.Cache.BlockNumber - alias Explorer.Helper, as: ExplorerHelper alias Explorer.Utility.{MissingBlockRange, MissingRangesManipulator} - alias Indexer.Block.Catchup.Helper @default_missing_ranges_batch_size 100_000 @future_check_interval Application.compile_env(:indexer, __MODULE__)[:future_check_interval] @@ -233,7 +232,7 @@ defmodule Indexer.Block.Catchup.MissingRangesCollector do |> Enum.map(fn string_range -> case String.split(string_range, "..") do [from_string, "latest"] -> - ExplorerHelper.parse_integer(from_string) + Helper.parse_integer(from_string) [from_string, to_string] -> get_from_to(from_string, to_string) @@ -242,7 +241,7 @@ defmodule Indexer.Block.Catchup.MissingRangesCollector do nil end end) - |> Helper.sanitize_ranges() + |> RangesHelper.sanitize_ranges() case List.last(ranges) do _from.._to -> diff --git a/apps/indexer/lib/indexer/block/fetcher.ex b/apps/indexer/lib/indexer/block/fetcher.ex index 713880e401d2..11261231247d 100644 --- a/apps/indexer/lib/indexer/block/fetcher.ex +++ b/apps/indexer/lib/indexer/block/fetcher.ex @@ -190,7 +190,7 @@ defmodule Indexer.Block.Fetcher do block_rewards: %{errors: beneficiaries_errors, params: beneficiaries_with_gas_payment}, logs: %{params: logs}, token_transfers: %{params: token_transfers}, - tokens: %{on_conflict: :nothing, params: tokens}, + tokens: %{params: tokens}, transactions: %{params: transactions_with_receipts}, withdrawals: %{params: withdrawals_params}, token_instances: %{params: token_instances} @@ -407,15 +407,15 @@ defmodule Indexer.Block.Fetcher do def fetch_beneficiaries_manual(block, transactions) do block - |> Chain.block_reward_by_parts(transactions) + |> Block.block_reward_by_parts(transactions) |> reward_parts_to_beneficiaries() end defp reward_parts_to_beneficiaries(reward_parts) do reward = reward_parts.static_reward - |> Wei.sum(reward_parts.txn_fees) - |> Wei.sub(reward_parts.burned_fees) + |> Wei.sum(reward_parts.transaction_fees) + |> Wei.sub(reward_parts.burnt_fees) |> Wei.sum(reward_parts.uncle_reward) MapSet.new([ diff --git a/apps/indexer/lib/indexer/fetcher/block_reward.ex b/apps/indexer/lib/indexer/fetcher/block_reward.ex index 2558b9510fee..e179538b2ccf 100644 --- a/apps/indexer/lib/indexer/fetcher/block_reward.ex +++ b/apps/indexer/lib/indexer/fetcher/block_reward.ex @@ -20,8 +20,8 @@ defmodule Indexer.Fetcher.BlockReward do alias Explorer.Chain.Cache.Accounts alias Indexer.{BufferedTask, Tracer} alias Indexer.Fetcher.BlockReward.Supervisor, as: BlockRewardSupervisor - alias Indexer.Fetcher.{CoinBalance, CoinBalanceDailyUpdater} - alias Indexer.Transform.{AddressCoinBalances, AddressCoinBalancesDaily, Addresses} + alias Indexer.Fetcher.CoinBalance + alias Indexer.Transform.{AddressCoinBalances, Addresses} @behaviour BufferedTask @@ -274,26 +274,6 @@ defmodule Indexer.Fetcher.BlockReward do addresses_params = Addresses.extract_addresses(%{block_reward_contract_beneficiaries: block_rewards_params}) address_coin_balances_params_set = AddressCoinBalances.params_set(%{beneficiary_params: block_rewards_params}) - address_coin_balances_params_with_block_timestamp = - block_rewards_params - |> Enum.map(fn block_rewards_param -> - %{ - address_hash: block_rewards_param.address_hash, - block_number: block_rewards_param.block_number, - block_timestamp: block_rewards_param.block_timestamp - } - end) - |> Enum.into(MapSet.new()) - - address_coin_balances_params_with_block_timestamp_set = %{ - address_coin_balances_params_with_block_timestamp: address_coin_balances_params_with_block_timestamp - } - - address_coin_balances_daily_params_set = - AddressCoinBalancesDaily.params_set(address_coin_balances_params_with_block_timestamp_set) - - CoinBalanceDailyUpdater.add_daily_balances_params(address_coin_balances_daily_params_set) - Chain.import(%{ addresses: %{params: addresses_params}, address_coin_balances: %{params: address_coin_balances_params_set}, diff --git a/apps/indexer/lib/indexer/fetcher/coin_balance.ex b/apps/indexer/lib/indexer/fetcher/coin_balance.ex index 18de40eaaf16..3d7171724547 100644 --- a/apps/indexer/lib/indexer/fetcher/coin_balance.ex +++ b/apps/indexer/lib/indexer/fetcher/coin_balance.ex @@ -11,7 +11,7 @@ defmodule Indexer.Fetcher.CoinBalance do import EthereumJSONRPC, only: [integer_to_quantity: 1, quantity_to_integer: 1] - alias EthereumJSONRPC.{Blocks, FetchedBalances} + alias EthereumJSONRPC.{Blocks, FetchedBalances, Utility.RangesHelper} alias Explorer.Chain alias Explorer.Chain.{Block, Hash} alias Explorer.Chain.Cache.Accounts @@ -83,13 +83,8 @@ defmodule Indexer.Fetcher.CoinBalance do # `{address, block}`, so take unique params only unique_entries = Enum.uniq(entries) - min_block = Application.get_env(:indexer, :trace_first_block) - max_block = Application.get_env(:indexer, :trace_last_block) - unique_filtered_entries = - Enum.filter(unique_entries, fn {_hash, block_number} -> - block_number >= min_block && if max_block, do: block_number <= max_block, else: true - end) + Enum.filter(unique_entries, fn {_hash, block_number} -> RangesHelper.traceable_block_number?(block_number) end) unique_entry_count = Enum.count(unique_filtered_entries) Logger.metadata(count: unique_entry_count) diff --git a/apps/indexer/lib/indexer/fetcher/contract_code.ex b/apps/indexer/lib/indexer/fetcher/contract_code.ex index c9f8f0f7b6e8..716b8f25e204 100644 --- a/apps/indexer/lib/indexer/fetcher/contract_code.ex +++ b/apps/indexer/lib/indexer/fetcher/contract_code.ex @@ -98,6 +98,7 @@ defmodule Indexer.Fetcher.ContractCode do Logger.debug("fetching created_contract_code for transactions") entries + |> Enum.uniq() |> Enum.map(¶ms/1) |> EthereumJSONRPC.fetch_codes(json_rpc_named_arguments) |> case do diff --git a/apps/indexer/lib/indexer/fetcher/internal_transaction.ex b/apps/indexer/lib/indexer/fetcher/internal_transaction.ex index e4726d5a3c7a..1861018b3324 100644 --- a/apps/indexer/lib/indexer/fetcher/internal_transaction.ex +++ b/apps/indexer/lib/indexer/fetcher/internal_transaction.ex @@ -12,6 +12,7 @@ defmodule Indexer.Fetcher.InternalTransaction do import Indexer.Block.Fetcher, only: [async_import_coin_balances: 2] + alias EthereumJSONRPC.Utility.RangesHelper alias Explorer.Chain alias Explorer.Chain.Block alias Explorer.Chain.Cache.{Accounts, Blocks} @@ -100,7 +101,7 @@ defmodule Indexer.Fetcher.InternalTransaction do filtered_unique_numbers = unique_numbers - |> EthereumJSONRPC.are_block_numbers_in_range?() + |> RangesHelper.filter_traceable_block_numbers() |> drop_genesis(json_rpc_named_arguments) filtered_unique_numbers_count = Enum.count(filtered_unique_numbers) @@ -363,8 +364,9 @@ defmodule Indexer.Fetcher.InternalTransaction do defp invalidate_block_from_error(_error_data), do: :ok - defp defaults do + def defaults do [ + poll: false, flush_interval: :timer.seconds(3), max_concurrency: Application.get_env(:indexer, __MODULE__)[:concurrency] || @default_max_concurrency, max_batch_size: Application.get_env(:indexer, __MODULE__)[:batch_size] || @default_max_batch_size, diff --git a/apps/indexer/lib/indexer/fetcher/polygon_edge/deposit_execute.ex b/apps/indexer/lib/indexer/fetcher/polygon_edge/deposit_execute.ex index 4e2dd57d09eb..ef1ad37e4a4c 100644 --- a/apps/indexer/lib/indexer/fetcher/polygon_edge/deposit_execute.ex +++ b/apps/indexer/lib/indexer/fetcher/polygon_edge/deposit_execute.ex @@ -12,6 +12,7 @@ defmodule Indexer.Fetcher.PolygonEdge.DepositExecute do import EthereumJSONRPC, only: [quantity_to_integer: 1] import Indexer.Fetcher.PolygonEdge, only: [fill_block_range: 5, get_block_number_by_tag: 3] + import Indexer.Helper, only: [log_topic_to_string: 1] alias Explorer.{Chain, Repo} alias Explorer.Chain.Log @@ -128,8 +129,13 @@ defmodule Indexer.Fetcher.PolygonEdge.DepositExecute do @spec event_to_deposit_execute(binary(), binary(), binary(), binary()) :: map() def event_to_deposit_execute(second_topic, third_topic, l2_transaction_hash, l2_block_number) do + msg_id = + second_topic + |> log_topic_to_string() + |> quantity_to_integer() + %{ - msg_id: quantity_to_integer(second_topic), + msg_id: msg_id, l2_transaction_hash: l2_transaction_hash, l2_block_number: quantity_to_integer(l2_block_number), success: quantity_to_integer(third_topic) != 0 diff --git a/apps/indexer/lib/indexer/fetcher/polygon_edge/withdrawal.ex b/apps/indexer/lib/indexer/fetcher/polygon_edge/withdrawal.ex index 80fb20568c99..098545140ac0 100644 --- a/apps/indexer/lib/indexer/fetcher/polygon_edge/withdrawal.ex +++ b/apps/indexer/lib/indexer/fetcher/polygon_edge/withdrawal.ex @@ -13,6 +13,7 @@ defmodule Indexer.Fetcher.PolygonEdge.Withdrawal do import EthereumJSONRPC, only: [quantity_to_integer: 1] import Explorer.Helper, only: [decode_data: 2] import Indexer.Fetcher.PolygonEdge, only: [fill_block_range: 5, get_block_number_by_tag: 3] + import Indexer.Helper, only: [log_topic_to_string: 1] alias ABI.TypeDecoder alias Explorer.{Chain, Repo} @@ -133,6 +134,11 @@ defmodule Indexer.Fetcher.PolygonEdge.Withdrawal do @spec event_to_withdrawal(binary(), map(), binary(), binary()) :: map() def event_to_withdrawal(second_topic, data, l2_transaction_hash, l2_block_number) do + msg_id = + second_topic + |> log_topic_to_string() + |> quantity_to_integer() + [data_bytes] = decode_data(data, [:bytes]) sig = binary_part(data_bytes, 0, 32) @@ -148,7 +154,7 @@ defmodule Indexer.Fetcher.PolygonEdge.Withdrawal do end %{ - msg_id: quantity_to_integer(second_topic), + msg_id: msg_id, from: from, to: to, l2_transaction_hash: l2_transaction_hash, diff --git a/apps/indexer/lib/indexer/fetcher/token_balance_on_demand.ex b/apps/indexer/lib/indexer/fetcher/token_balance_on_demand.ex index d1dfcf6d3364..cd090f19fe36 100644 --- a/apps/indexer/lib/indexer/fetcher/token_balance_on_demand.ex +++ b/apps/indexer/lib/indexer/fetcher/token_balance_on_demand.ex @@ -6,7 +6,7 @@ defmodule Indexer.Fetcher.TokenBalanceOnDemand do use Indexer.Fetcher - alias Explorer.Chain + alias Explorer.{Chain, Repo} alias Explorer.Chain.Address.CurrentTokenBalance alias Explorer.Chain.Cache.BlockNumber alias Explorer.Chain.Events.Publisher @@ -52,6 +52,7 @@ defmodule Indexer.Fetcher.TokenBalanceOnDemand do stale_current_token_balances = address_hash |> Chain.fetch_last_token_balances_include_unfetched() + |> delete_invalid_balances() |> Enum.filter(fn current_token_balance -> current_token_balance.block_number < stale_balance_window end) if Enum.count(stale_current_token_balances) > 0 do @@ -63,6 +64,12 @@ defmodule Indexer.Fetcher.TokenBalanceOnDemand do :ok end + defp delete_invalid_balances(current_token_balances) do + {invalid_balances, valid_balances} = Enum.split_with(current_token_balances, &is_nil(&1.token_type)) + Enum.each(invalid_balances, &Repo.delete/1) + valid_balances + end + defp fetch_and_update(block_number, address_hash, stale_current_token_balances) do %{erc_1155: erc_1155_ctbs, other: other_ctbs, tokens: tokens} = stale_current_token_balances diff --git a/apps/indexer/lib/indexer/fetcher/token_instance/helper.ex b/apps/indexer/lib/indexer/fetcher/token_instance/helper.ex index e5e2256ff9eb..792f92c85ada 100644 --- a/apps/indexer/lib/indexer/fetcher/token_instance/helper.ex +++ b/apps/indexer/lib/indexer/fetcher/token_instance/helper.ex @@ -6,6 +6,8 @@ defmodule Indexer.Fetcher.TokenInstance.Helper do alias Explorer.SmartContract.Reader alias Indexer.Fetcher.TokenInstance.MetadataRetriever + require Logger + @cryptokitties_address_hash "0x06012c8cf97bead5deae237070f9587f8e7a266d" @token_uri "c87b56dd" @@ -107,19 +109,21 @@ defmodule Indexer.Fetcher.TokenInstance.Helper do |> Task.yield_many(:infinity) |> Enum.zip(contract_results) |> Enum.map(fn {{_task, res}, {_result, _normalized_token_id, contract_address_hash, token_id}} -> - case res do - {:ok, result} -> - result_to_insert_params(result, contract_address_hash, token_id) - - {:exit, reason} -> - result_to_insert_params( - {:error, MetadataRetriever.truncate_error("Terminated:" <> inspect(reason))}, - contract_address_hash, - token_id - ) - end + insert_params = + case res do + {:ok, result} -> + result_to_insert_params(result, contract_address_hash, token_id) + + {:exit, reason} -> + result_to_insert_params( + {:error, MetadataRetriever.truncate_error("Terminated:" <> inspect(reason))}, + contract_address_hash, + token_id + ) + end + + upsert_with_rescue(insert_params, token_id, contract_address_hash) end) - |> Chain.upsert_token_instances_list() end defp prepare_token_id(%Decimal{} = token_id), do: Decimal.to_integer(token_id) @@ -170,4 +174,21 @@ defmodule Indexer.Fetcher.TokenInstance.Helper do error: error } end + + defp upsert_with_rescue(insert_params, token_id, token_contract_address_hash, retrying? \\ false) do + Chain.upsert_token_instance(insert_params) + rescue + error in Postgrex.Error -> + if retrying? do + Logger.warn(["Failed to upsert token instance: #{inspect(error)}"], fetcher: :token_instances) + nil + else + token_id + |> token_instance_map_with_error( + token_contract_address_hash, + MetadataRetriever.truncate_error(inspect(error.postgres.code)) + ) + |> upsert_with_rescue(token_id, token_contract_address_hash, true) + end + end end diff --git a/apps/indexer/lib/indexer/fetcher/token_instance/legacy_sanitize.ex b/apps/indexer/lib/indexer/fetcher/token_instance/legacy_sanitize.ex index 1fe11e1d90dc..391353ba3788 100644 --- a/apps/indexer/lib/indexer/fetcher/token_instance/legacy_sanitize.ex +++ b/apps/indexer/lib/indexer/fetcher/token_instance/legacy_sanitize.ex @@ -1,58 +1,50 @@ defmodule Indexer.Fetcher.TokenInstance.LegacySanitize do @moduledoc """ - This fetcher is stands for creating token instances which wasn't inserted yet and index meta for them. Legacy is because now we token instances inserted on block import and this fetcher is only for historical and unfetched for some reasons data + This fetcher is stands for creating token instances which wasn't inserted yet and index meta for them. + Legacy is because now we token instances inserted on block import and this fetcher is only for historical and unfetched for some reasons data """ - use Indexer.Fetcher, restart: :permanent - use Spandex.Decorators + use GenServer, restart: :transient - import Indexer.Fetcher.TokenInstance.Helper - - alias Explorer.Chain - alias Indexer.BufferedTask - - @behaviour BufferedTask + alias Explorer.Chain.Token.Instance + alias Explorer.Repo - @default_max_batch_size 10 - @default_max_concurrency 10 - @doc false - def child_spec([init_options, gen_server_options]) do - merged_init_opts = - defaults() - |> Keyword.merge(init_options) - |> Keyword.merge(state: []) + import Indexer.Fetcher.TokenInstance.Helper - Supervisor.child_spec({BufferedTask, [{__MODULE__, merged_init_opts}, gen_server_options]}, id: __MODULE__) + def start_link(_) do + concurrency = Application.get_env(:indexer, __MODULE__)[:concurrency] + batch_size = Application.get_env(:indexer, __MODULE__)[:batch_size] + GenServer.start_link(__MODULE__, %{concurrency: concurrency, batch_size: batch_size}, name: __MODULE__) end - @impl BufferedTask - def init(initial_acc, reducer, _) do - {:ok, acc} = - Chain.stream_not_inserted_token_instances(initial_acc, fn data, acc -> - reducer.(data, acc) - end) + @impl true + def init(opts) do + GenServer.cast(__MODULE__, :backfill) - acc + {:ok, opts} end - @impl BufferedTask - def run(token_instances, _) when is_list(token_instances) do - token_instances - |> Enum.filter(fn %{contract_address_hash: hash, token_id: token_id} -> - not Chain.token_instance_exists?(token_id, hash) - end) - |> batch_fetch_instances() - - :ok + @impl true + def handle_cast(:backfill, %{concurrency: concurrency, batch_size: batch_size} = state) do + instances_to_fetch = + (concurrency * batch_size) + |> Instance.not_inserted_token_instances_query() + |> Repo.all() + + if Enum.empty?(instances_to_fetch) do + {:stop, :normal, state} + else + instances_to_fetch + |> Enum.uniq() + |> Enum.chunk_every(batch_size) + |> Enum.map(&process_batch/1) + |> Task.await_many(:infinity) + + GenServer.cast(__MODULE__, :backfill) + + {:noreply, state} + end end - defp defaults do - [ - flush_interval: :infinity, - max_concurrency: Application.get_env(:indexer, __MODULE__)[:concurrency] || @default_max_concurrency, - max_batch_size: Application.get_env(:indexer, __MODULE__)[:batch_size] || @default_max_batch_size, - poll: false, - task_supervisor: __MODULE__.TaskSupervisor - ] - end + defp process_batch(batch), do: Task.async(fn -> batch_fetch_instances(batch) end) end diff --git a/apps/indexer/lib/indexer/fetcher/token_instance/metadata_retriever.ex b/apps/indexer/lib/indexer/fetcher/token_instance/metadata_retriever.ex index 975b7bed5f87..a4812fb332a5 100644 --- a/apps/indexer/lib/indexer/fetcher/token_instance/metadata_retriever.ex +++ b/apps/indexer/lib/indexer/fetcher/token_instance/metadata_retriever.ex @@ -5,6 +5,7 @@ defmodule Indexer.Fetcher.TokenInstance.MetadataRetriever do require Logger + alias Explorer.Helper, as: ExplorerHelper alias Explorer.SmartContract.Reader alias HTTPoison.{Error, Response} @@ -52,7 +53,7 @@ defmodule Indexer.Fetcher.TokenInstance.MetadataRetriever do if error =~ "execution reverted" or error =~ @vm_execution_error do {:error, @vm_execution_error} else - Logger.debug(["Unknown metadata format error #{inspect(error)}."], fetcher: :token_instances) + Logger.warn(["Unknown metadata format error #{inspect(error)}."], fetcher: :token_instances) # truncate error since it will be stored in DB {:error, truncate_error(error)} @@ -64,7 +65,7 @@ defmodule Indexer.Fetcher.TokenInstance.MetadataRetriever do if String.length(result) == 46 do fetch_json_from_uri({:ok, [ipfs_link() <> result]}, hex_token_id) else - Logger.debug(["Unknown metadata format result #{inspect(result)}."], fetcher: :token_instances) + Logger.warn(["Unknown metadata format result #{inspect(result)}."], fetcher: :token_instances) {:error, truncate_error(result)} end @@ -89,7 +90,7 @@ defmodule Indexer.Fetcher.TokenInstance.MetadataRetriever do fetch_json_from_uri({:ok, [decoded_json]}, hex_token_id) rescue e -> - Logger.debug(["Unknown metadata format #{inspect(json)}.", Exception.format(:error, e, __STACKTRACE__)], + Logger.warn(["Unknown metadata format #{inspect(json)}.", Exception.format(:error, e, __STACKTRACE__)], fetcher: :token_instances ) @@ -106,7 +107,7 @@ defmodule Indexer.Fetcher.TokenInstance.MetadataRetriever do end rescue e -> - Logger.debug( + Logger.warn( [ "Unknown metadata format base64 #{inspect(base64_encoded_json)}.", Exception.format(:error, e, __STACKTRACE__) @@ -130,12 +131,12 @@ defmodule Indexer.Fetcher.TokenInstance.MetadataRetriever do end defp fetch_json_from_uri({:ok, [json]}, hex_token_id) do - {:ok, json} = decode_json(json) + json = ExplorerHelper.decode_json(json) check_type(json, hex_token_id) rescue e -> - Logger.debug(["Unknown metadata format #{inspect(json)}.", Exception.format(:error, e, __STACKTRACE__)], + Logger.warn(["Unknown metadata format #{inspect(json)}.", Exception.format(:error, e, __STACKTRACE__)], fetcher: :token_instances ) @@ -143,7 +144,7 @@ defmodule Indexer.Fetcher.TokenInstance.MetadataRetriever do end defp fetch_json_from_uri(uri, _hex_token_id) do - Logger.debug(["Unknown metadata uri format #{inspect(uri)}."], fetcher: :token_instances) + Logger.warn(["Unknown metadata uri format #{inspect(uri)}."], fetcher: :token_instances) {:error, "unknown metadata uri format"} end @@ -158,7 +159,7 @@ defmodule Indexer.Fetcher.TokenInstance.MetadataRetriever do fetch_metadata_from_uri(prepared_uri, hex_token_id) rescue e -> - Logger.debug( + Logger.warn( ["Could not prepare token uri #{inspect(uri)}.", Exception.format(:error, e, __STACKTRACE__)], fetcher: :token_instances ) @@ -188,7 +189,7 @@ defmodule Indexer.Fetcher.TokenInstance.MetadataRetriever do check_content_type(content_type, uri, hex_token_id, body) {:ok, %Response{body: body, status_code: code}} -> - Logger.debug( + Logger.warn( ["Request to token uri: #{inspect(uri)} failed with code #{code}. Body:", inspect(body)], fetcher: :token_instances ) @@ -196,7 +197,7 @@ defmodule Indexer.Fetcher.TokenInstance.MetadataRetriever do {:error_code, code} {:error, %Error{reason: reason}} -> - Logger.debug( + Logger.warn( ["Request to token uri failed: #{inspect(uri)}.", inspect(reason)], fetcher: :token_instances ) @@ -205,7 +206,7 @@ defmodule Indexer.Fetcher.TokenInstance.MetadataRetriever do end rescue e -> - Logger.debug( + Logger.warn( ["Could not send request to token uri #{inspect(uri)}.", Exception.format(:error, e, __STACKTRACE__)], fetcher: :token_instances ) @@ -222,7 +223,7 @@ defmodule Indexer.Fetcher.TokenInstance.MetadataRetriever do check_type(json, nil) else - {:ok, json} = decode_json(body) + json = ExplorerHelper.decode_json(body) check_type(json, hex_token_id) end @@ -245,16 +246,6 @@ defmodule Indexer.Fetcher.TokenInstance.MetadataRetriever do content_type && String.starts_with?(content_type, "video/") end - defp decode_json(body) do - if String.valid?(body) do - Jason.decode(body) - else - body - |> :unicode.characters_to_binary(:latin1) - |> Jason.decode() - end - end - defp check_type(json, nil) when is_map(json) do {:ok, %{metadata: json}} end diff --git a/apps/indexer/lib/indexer/helper.ex b/apps/indexer/lib/indexer/helper.ex index 540606fe222f..21b69831f26e 100644 --- a/apps/indexer/lib/indexer/helper.ex +++ b/apps/indexer/lib/indexer/helper.ex @@ -32,4 +32,13 @@ defmodule Indexer.Helper do def is_address_correct?(_address) do false end + + @spec log_topic_to_string(any()) :: binary() | nil + def log_topic_to_string(topic) do + if is_binary(topic) or is_nil(topic) do + topic + else + Hash.to_string(topic) + end + end end diff --git a/apps/indexer/lib/indexer/supervisor.ex b/apps/indexer/lib/indexer/supervisor.ex index 007eab9e5db3..10a745512bd0 100644 --- a/apps/indexer/lib/indexer/supervisor.ex +++ b/apps/indexer/lib/indexer/supervisor.ex @@ -120,7 +120,7 @@ defmodule Indexer.Supervisor do {TokenInstanceRealtime.Supervisor, [[memory_monitor: memory_monitor]]}, {TokenInstanceRetry.Supervisor, [[memory_monitor: memory_monitor]]}, {TokenInstanceSanitize.Supervisor, [[memory_monitor: memory_monitor]]}, - {TokenInstanceLegacySanitize.Supervisor, [[memory_monitor: memory_monitor]]}, + {TokenInstanceLegacySanitize, [[memory_monitor: memory_monitor]]}, configure(TransactionAction.Supervisor, [[memory_monitor: memory_monitor]]), {ContractCode.Supervisor, [[json_rpc_named_arguments: json_rpc_named_arguments, memory_monitor: memory_monitor]]}, diff --git a/apps/indexer/lib/indexer/temporary/uncataloged_token_transfers.ex b/apps/indexer/lib/indexer/temporary/uncataloged_token_transfers.ex index 17f676f136d5..d048a3331172 100644 --- a/apps/indexer/lib/indexer/temporary/uncataloged_token_transfers.ex +++ b/apps/indexer/lib/indexer/temporary/uncataloged_token_transfers.ex @@ -12,7 +12,7 @@ defmodule Indexer.Temporary.UncatalogedTokenTransfers do require Logger - alias Explorer.Chain + alias Explorer.Chain.TokenTransfer alias Indexer.Block.Catchup.Fetcher alias Indexer.Temporary.UncatalogedTokenTransfers @@ -52,7 +52,7 @@ defmodule Indexer.Temporary.UncatalogedTokenTransfers do end def handle_info(:scan, state) do - {:ok, block_numbers} = Chain.uncataloged_token_transfer_block_numbers() + {:ok, block_numbers} = TokenTransfer.uncataloged_token_transfer_block_numbers() case block_numbers do [] -> diff --git a/apps/indexer/lib/indexer/transform/mint_transfers.ex b/apps/indexer/lib/indexer/transform/mint_transfers.ex index e57a9841a7fb..c53dde7a5387 100644 --- a/apps/indexer/lib/indexer/transform/mint_transfers.ex +++ b/apps/indexer/lib/indexer/transform/mint_transfers.ex @@ -24,8 +24,7 @@ defmodule Indexer.Transform.MintTransfers do ...> index: 1, ...> second_topic: "0x0000000000000000000000009a4a90e2732f3fa4087b0bb4bf85c76d14833df1", ...> third_topic: "0x0000000000000000000000007301cfa0e1756b71869e93d4e4dca5c7d0eb0aa6", - ...> transaction_hash: "0x1d5066d30ff3404a9306733136103ac2b0b989951c38df637f464f3667f8d4ee", - ...> type: "mined" + ...> transaction_hash: "0x1d5066d30ff3404a9306733136103ac2b0b989951c38df637f464f3667f8d4ee" ...> } ...> ]) %{ diff --git a/apps/indexer/lib/indexer/transform/token_transfers.ex b/apps/indexer/lib/indexer/transform/token_transfers.ex index fc6fb6a6cfe7..7b10e701b9a4 100644 --- a/apps/indexer/lib/indexer/transform/token_transfers.ex +++ b/apps/indexer/lib/indexer/transform/token_transfers.ex @@ -81,22 +81,18 @@ defmodule Indexer.Transform.TokenTransfers do end) |> Map.new() - existing_tokens = - existing_token_types_map - |> Map.keys() - |> Enum.map(&to_string/1) - - new_tokens_token_transfers = Enum.filter(token_transfers, &(&1.token_contract_address_hash not in existing_tokens)) - - new_token_types_map = - new_tokens_token_transfers + token_types_map = + token_transfers |> Enum.group_by(& &1.token_contract_address_hash) |> Enum.map(fn {contract_address_hash, transfers} -> {contract_address_hash, define_token_type(transfers)} end) |> Map.new() - actual_token_types_map = Map.merge(new_token_types_map, existing_token_types_map) + actual_token_types_map = + Map.merge(token_types_map, existing_token_types_map, fn _k, new_type, old_type -> + if token_type_priority(old_type) > token_type_priority(new_type), do: old_type, else: new_type + end) actual_tokens = Enum.map(tokens, fn %{contract_address_hash: hash} = token -> diff --git a/apps/indexer/lib/indexer/transform/transaction_actions.ex b/apps/indexer/lib/indexer/transform/transaction_actions.ex index 1620fd724dd2..618c311f2a95 100644 --- a/apps/indexer/lib/indexer/transform/transaction_actions.ex +++ b/apps/indexer/lib/indexer/transform/transaction_actions.ex @@ -285,8 +285,15 @@ defmodule Indexer.Transform.TransactionActions do [debt_amount, collateral_amount, _liquidator, _receive_a_token] = decode_data(log.data, [{:uint, 256}, {:uint, 256}, :address, :bool]) - debt_address = truncate_address_hash(log.third_topic) - collateral_address = truncate_address_hash(log.second_topic) + debt_address = + log.third_topic + |> Helper.log_topic_to_string() + |> truncate_address_hash() + + collateral_address = + log.second_topic + |> Helper.log_topic_to_string() + |> truncate_address_hash() case get_token_data([debt_address, collateral_address]) do false -> @@ -318,7 +325,10 @@ defmodule Indexer.Transform.TransactionActions do defp aave_handle_event(type, amount, log, address_topic, chain_id) when type in ["borrow", "supply", "withdraw", "repay", "flash_loan"] do - address = truncate_address_hash(address_topic) + address = + address_topic + |> Helper.log_topic_to_string() + |> truncate_address_hash() case get_token_data([address]) do false -> @@ -345,7 +355,10 @@ defmodule Indexer.Transform.TransactionActions do end defp aave_handle_event(type, log, address_topic, chain_id) when type in ["enable_collateral", "disable_collateral"] do - address = truncate_address_hash(address_topic) + address = + address_topic + |> Helper.log_topic_to_string() + |> truncate_address_hash() case get_token_data([address]) do false -> @@ -448,12 +461,23 @@ defmodule Indexer.Transform.TransactionActions do |> Enum.reduce(%{}, fn log, acc -> if sanitize_first_topic(log.first_topic) == @uniswap_v3_transfer_nft_event do # This is Transfer event for NFT - from = truncate_address_hash(log.second_topic) + from = + log.second_topic + |> Helper.log_topic_to_string() + |> truncate_address_hash() # credo:disable-for-next-line if from == burn_address_hash_string() do - to = truncate_address_hash(log.third_topic) - [token_id] = decode_data(log.fourth_topic, [{:uint, 256}]) + to = + log.third_topic + |> Helper.log_topic_to_string() + |> truncate_address_hash() + + [token_id] = + log.fourth_topic + |> Helper.log_topic_to_string() + |> decode_data([{:uint, 256}]) + mint_nft_ids = Map.put_new(acc, to, %{ids: [], log_index: log.index}) Map.put(mint_nft_ids, to, %{ @@ -970,7 +994,7 @@ defmodule Indexer.Transform.TransactionActions do end defp sanitize_first_topic(first_topic) do - if is_nil(first_topic), do: "", else: String.downcase(first_topic) + if is_nil(first_topic), do: "", else: String.downcase(Helper.log_topic_to_string(first_topic)) end defp truncate_address_hash(nil), do: burn_address_hash_string() diff --git a/apps/indexer/mix.exs b/apps/indexer/mix.exs index e3454f3318eb..7e349261ccbd 100644 --- a/apps/indexer/mix.exs +++ b/apps/indexer/mix.exs @@ -14,7 +14,7 @@ defmodule Indexer.MixProject do elixirc_paths: elixirc_paths(Mix.env()), lockfile: "../../mix.lock", start_permanent: Mix.env() == :prod, - version: "5.3.2" + version: "6.0.0" ] end diff --git a/apps/indexer/test/indexer/block/fetcher/receipts_test.exs b/apps/indexer/test/indexer/block/fetcher/receipts_test.exs index c70d10761a01..a7c45106551e 100644 --- a/apps/indexer/test/indexer/block/fetcher/receipts_test.exs +++ b/apps/indexer/test/indexer/block/fetcher/receipts_test.exs @@ -51,8 +51,7 @@ defmodule Indexer.Block.Fetcher.ReceiptsTest do "topics" => ["0x600bcf04a13e752d1e3670a5a9f1c21177ca2a93c6f5391d4f1298d098097c22"], "transactionHash" => "0x43bd884872de3e488692881baeec262e7b95234d3965248c39fe992fffd433e5", "transactionIndex" => "0x0", - "transactionLogIndex" => "0x0", - "type" => "mined" + "transactionLogIndex" => "0x0" } ], "logsBloom" => @@ -82,8 +81,7 @@ defmodule Indexer.Block.Fetcher.ReceiptsTest do "topics" => ["0x600bcf04a13e752d1e3670a5a9f1c21177ca2a93c6f5391d4f1298d098097c22"], "transactionHash" => "0x53bd884872de3e488692881baeec262e7b95234d3965248c39fe992fffd433e5", "transactionIndex" => "0x0", - "transactionLogIndex" => "0x0", - "type" => "mined" + "transactionLogIndex" => "0x0" } ], "logsBloom" => diff --git a/apps/indexer/test/indexer/block/fetcher_test.exs b/apps/indexer/test/indexer/block/fetcher_test.exs index 24be0dfa8803..0498cfd37ecb 100644 --- a/apps/indexer/test/indexer/block/fetcher_test.exs +++ b/apps/indexer/test/indexer/block/fetcher_test.exs @@ -375,8 +375,7 @@ defmodule Indexer.Block.FetcherTest do "topics" => ["0x600bcf04a13e752d1e3670a5a9f1c21177ca2a93c6f5391d4f1298d098097c22"], "transactionHash" => "0x53bd884872de3e488692881baeec262e7b95234d3965248c39fe992fffd433e5", "transactionIndex" => "0x0", - "transactionLogIndex" => "0x0", - "type" => "mined" + "transactionLogIndex" => "0x0" } ], "logsBloom" => diff --git a/apps/indexer/test/indexer/fetcher/internal_transaction_test.exs b/apps/indexer/test/indexer/fetcher/internal_transaction_test.exs index b36006b872f6..e451525869b1 100644 --- a/apps/indexer/test/indexer/fetcher/internal_transaction_test.exs +++ b/apps/indexer/test/indexer/fetcher/internal_transaction_test.exs @@ -5,8 +5,10 @@ defmodule Indexer.Fetcher.InternalTransactionTest do import ExUnit.CaptureLog import Mox - alias Explorer.Chain - alias Explorer.Chain.PendingBlockOperation + alias Ecto.Multi + alias Explorer.{Chain, Repo} + alias Explorer.Chain.{Block, PendingBlockOperation} + alias Explorer.Chain.Import.Runner.Blocks alias Indexer.Fetcher.{CoinBalance, InternalTransaction, PendingTransaction} # MUST use global mode because we aren't guaranteed to get PendingTransactionFetcher's pid back fast enough to `allow` @@ -466,4 +468,39 @@ defmodule Indexer.Fetcher.InternalTransactionTest do assert logs =~ "foreign_key_violation on internal transactions import, foreign transactions hashes:" end end + + test "doesn't delete pending block operations after block import if no async process was requested", %{ + json_rpc_named_arguments: json_rpc_named_arguments + } do + fetcher_options = + Keyword.merge([poll: true, json_rpc_named_arguments: json_rpc_named_arguments], InternalTransaction.defaults()) + + if fetcher_options[:poll] do + expect(EthereumJSONRPC.Mox, :json_rpc, fn [%{id: id}], _options -> + {:ok, [%{id: id, result: []}]} + end) + end + + InternalTransaction.Supervisor.Case.start_supervised!(fetcher_options) + + %Ecto.Changeset{valid?: true, changes: block_changes} = + Block.changeset(%Block{}, params_for(:block, miner_hash: insert(:address).hash, number: 1)) + + changes_list = [block_changes] + timestamp = DateTime.utc_now() + options = %{timestamps: %{inserted_at: timestamp, updated_at: timestamp}} + + assert [] = Repo.all(PendingBlockOperation) + + {:ok, %{blocks: [%{number: block_number, hash: block_hash}]}} = + Multi.new() + |> Blocks.run(changes_list, options) + |> Repo.transaction() + + assert %{block_number: ^block_number, block_hash: ^block_hash} = Repo.one(PendingBlockOperation) + + Process.sleep(4000) + + assert %{block_number: ^block_number, block_hash: ^block_hash} = Repo.one(PendingBlockOperation) + end end diff --git a/apps/indexer/test/indexer/transform/mint_transfers_test.exs b/apps/indexer/test/indexer/transform/mint_transfers_test.exs index 04499a7af454..4670e983a272 100644 --- a/apps/indexer/test/indexer/transform/mint_transfers_test.exs +++ b/apps/indexer/test/indexer/transform/mint_transfers_test.exs @@ -17,8 +17,7 @@ defmodule Indexer.Transform.MintTransfersTest do index: 1, second_topic: "0x0000000000000000000000009a4a90e2732f3fa4087b0bb4bf85c76d14833df1", third_topic: "0x0000000000000000000000007301cfa0e1756b71869e93d4e4dca5c7d0eb0aa6", - transaction_hash: "0x1d5066d30ff3404a9306733136103ac2b0b989951c38df637f464f3667f8d4ee", - type: "mined" + transaction_hash: "0x1d5066d30ff3404a9306733136103ac2b0b989951c38df637f464f3667f8d4ee" } ] @@ -47,8 +46,7 @@ defmodule Indexer.Transform.MintTransfersTest do index: 1, second_topic: "0x0000000000000000000000009a4a90e2732f3fa4087b0bb4bf85c76d14833df1", third_topic: "0x0000000000000000000000007301cfa0e1756b71869e93d4e4dca5c7d0eb0aa6", - transaction_hash: "0x1d5066d30ff3404a9306733136103ac2b0b989951c38df637f464f3667f8d4ee", - type: "mined" + transaction_hash: "0x1d5066d30ff3404a9306733136103ac2b0b989951c38df637f464f3667f8d4ee" } ] diff --git a/apps/indexer/test/indexer/transform/token_transfers_test.exs b/apps/indexer/test/indexer/transform/token_transfers_test.exs index 8833474acccc..0c670035cc35 100644 --- a/apps/indexer/test/indexer/transform/token_transfers_test.exs +++ b/apps/indexer/test/indexer/transform/token_transfers_test.exs @@ -19,8 +19,7 @@ defmodule Indexer.Transform.TokenTransfersTest do index: 8, second_topic: "0x000000000000000000000000556813d9cc20acfe8388af029a679d34a63388db", third_topic: "0x00000000000000000000000092148dd870fa1b7c4700f2bd7f44238821c26f73", - transaction_hash: "0x43dfd761974e8c3351d285ab65bee311454eb45b149a015fe7804a33252f19e5", - type: "mined" + transaction_hash: "0x43dfd761974e8c3351d285ab65bee311454eb45b149a015fe7804a33252f19e5" }, %{ address_hash: "0x6ea5ec9cb832e60b6b1654f5826e9be638f276a5", @@ -32,8 +31,7 @@ defmodule Indexer.Transform.TokenTransfersTest do index: 0, second_topic: "0x00000000000000000000000063b0595bb7a0b7edd0549c9557a0c8aee6da667b", third_topic: "0x000000000000000000000000f3089e15d0c23c181d7f98b0878b560bfe193a1d", - transaction_hash: "0x8425a9b81a9bd1c64861110c1a453b84719cb0361d6fa0db68abf7611b9a890e", - type: "mined" + transaction_hash: "0x8425a9b81a9bd1c64861110c1a453b84719cb0361d6fa0db68abf7611b9a890e" }, %{ address_hash: "0x91932e8c6776fb2b04abb71874a7988747728bb2", @@ -45,8 +43,7 @@ defmodule Indexer.Transform.TokenTransfersTest do index: 1, second_topic: "0x0000000000000000000000009851ba177554eb07271ac230a137551e6dd0aa84", third_topic: "0x000000000000000000000000dccb72afee70e60b0c1226288fe86c01b953e8ac", - transaction_hash: "0x4011d9a930a3da620321589a54dc0ca3b88216b4886c7a7c3aaad1fb17702d35", - type: "mined" + transaction_hash: "0x4011d9a930a3da620321589a54dc0ca3b88216b4886c7a7c3aaad1fb17702d35" }, %{ address_hash: "0x0BE9e53fd7EDaC9F859882AfdDa116645287C629", @@ -58,8 +55,7 @@ defmodule Indexer.Transform.TokenTransfersTest do third_topic: nil, fourth_topic: nil, index: 1, - transaction_hash: "0x185889bc91372106ecf114a4e23f4ee615e131ae3e698078bd5d2ed7e3f55a49", - type: "mined" + transaction_hash: "0x185889bc91372106ecf114a4e23f4ee615e131ae3e698078bd5d2ed7e3f55a49" }, %{ address_hash: "0x0BE9e53fd7EDaC9F859882AfdDa116645287C629", @@ -71,8 +67,7 @@ defmodule Indexer.Transform.TokenTransfersTest do third_topic: nil, fourth_topic: nil, index: 1, - transaction_hash: "0x07510dbfddbac9064f7d607c2d9a14aa26fa19cdfcd578c0b585ff2395df543f", - type: "mined" + transaction_hash: "0x07510dbfddbac9064f7d607c2d9a14aa26fa19cdfcd578c0b585ff2395df543f" } ] @@ -157,8 +152,7 @@ defmodule Indexer.Transform.TokenTransfersTest do second_topic: nil, third_topic: nil, transaction_hash: "0x6d2dd62c178e55a13b65601f227c4ffdd8aa4e3bcb1f24731363b4f7619e92c8", - block_hash: "0x79594150677f083756a37eee7b97ed99ab071f502104332cb3835bac345711ca", - type: "mined" + block_hash: "0x79594150677f083756a37eee7b97ed99ab071f502104332cb3835bac345711ca" } expected = %{ @@ -198,8 +192,7 @@ defmodule Indexer.Transform.TokenTransfersTest do fourth_topic: "0x0000000000000000000000009c978f4cfa1fe13406bcc05baf26a35716f881dd", index: 2, transaction_hash: "0x6d2dd62c178e55a13b65601f227c4ffdd8aa4e3bcb1f24731363b4f7619e92c8", - block_hash: "0x79594150677f083756a37eee7b97ed99ab071f502104332cb3835bac345711ca", - type: "mined" + block_hash: "0x79594150677f083756a37eee7b97ed99ab071f502104332cb3835bac345711ca" } assert TokenTransfers.parse([log]) == %{ @@ -240,8 +233,7 @@ defmodule Indexer.Transform.TokenTransfersTest do fourth_topic: "0x0000000000000000000000006c943470780461b00783ad530a53913bd2c104d3", index: 2, transaction_hash: "0x6d2dd62c178e55a13b65601f227c4ffdd8aa4e3bcb1f24731363b4f7619e92c8", - block_hash: "0x79594150677f083756a37eee7b97ed99ab071f502104332cb3835bac345711ca", - type: "mined" + block_hash: "0x79594150677f083756a37eee7b97ed99ab071f502104332cb3835bac345711ca" } assert TokenTransfers.parse([log]) == %{ @@ -275,8 +267,7 @@ defmodule Indexer.Transform.TokenTransfersTest do fourth_topic: "0x0000000000000000000000000000000000000000", index: 6, transaction_hash: "0xa6ad6588edb4abd8ca45f30d2f026ba20b68a3002a5870dbd30cc3752568483b", - block_hash: "0x61b720e40f8c521edd77a52cabce556c18b18b198f78e361f310003386ff1f02", - type: "mined" + block_hash: "0x61b720e40f8c521edd77a52cabce556c18b18b198f78e361f310003386ff1f02" } assert TokenTransfers.parse([log]) == %{ @@ -296,8 +287,7 @@ defmodule Indexer.Transform.TokenTransfersTest do index: 2, second_topic: nil, third_topic: nil, - transaction_hash: "0x6d2dd62c178e55a13b65601f227c4ffdd8aa4e3bcb1f24731363b4f7619e92c8", - type: "mined" + transaction_hash: "0x6d2dd62c178e55a13b65601f227c4ffdd8aa4e3bcb1f24731363b4f7619e92c8" } error = capture_log(fn -> %{tokens: [], token_transfers: []} = TokenTransfers.parse([log]) end) @@ -319,8 +309,7 @@ defmodule Indexer.Transform.TokenTransfersTest do index: 8, second_topic: "0x000000000000000000000000556813d9cc20acfe8388af029a679d34a63388db", third_topic: "0x00000000000000000000000092148dd870fa1b7c4700f2bd7f44238821c26f73", - transaction_hash: "0x43dfd761974e8c3351d285ab65bee311454eb45b149a015fe7804a33252f19e5", - type: "mined" + transaction_hash: "0x43dfd761974e8c3351d285ab65bee311454eb45b149a015fe7804a33252f19e5" } assert %{ @@ -343,8 +332,7 @@ defmodule Indexer.Transform.TokenTransfersTest do index: 8, second_topic: "0x000000000000000000000000556813d9cc20acfe8388af029a679d34a63388db", third_topic: "0x00000000000000000000000092148dd870fa1b7c4700f2bd7f44238821c26f73", - transaction_hash: "0x43dfd761974e8c3351d285ab65bee311454eb45b149a015fe7804a33252f19e5", - type: "mined" + transaction_hash: "0x43dfd761974e8c3351d285ab65bee311454eb45b149a015fe7804a33252f19e5" }, %{ address_hash: contract_address_hash, @@ -357,8 +345,7 @@ defmodule Indexer.Transform.TokenTransfersTest do fourth_topic: "0x0000000000000000000000009c978f4cfa1fe13406bcc05baf26a35716f881dd", index: 2, transaction_hash: "0x43dfd761974e8c3351d285ab65bee311454eb45b149a015fe7804a33252f19e5", - block_hash: "0x79594150677f083756a37eee7b97ed99ab071f502104332cb3835bac345711ca", - type: "mined" + block_hash: "0x79594150677f083756a37eee7b97ed99ab071f502104332cb3835bac345711ca" } ] diff --git a/apps/indexer/test/support/indexer/fetcher/internal_transaction_supervisor_case.ex b/apps/indexer/test/support/indexer/fetcher/internal_transaction_supervisor_case.ex index b067997b5fbf..0c3ba2be7358 100644 --- a/apps/indexer/test/support/indexer/fetcher/internal_transaction_supervisor_case.ex +++ b/apps/indexer/test/support/indexer/fetcher/internal_transaction_supervisor_case.ex @@ -4,11 +4,13 @@ defmodule Indexer.Fetcher.InternalTransaction.Supervisor.Case do def start_supervised!(fetcher_arguments \\ []) when is_list(fetcher_arguments) do merged_fetcher_arguments = Keyword.merge( - fetcher_arguments, - flush_interval: 50, - max_batch_size: 1, - max_concurrency: 1, - poll: false + [ + flush_interval: 50, + max_batch_size: 1, + max_concurrency: 1, + poll: false + ], + fetcher_arguments ) [merged_fetcher_arguments] diff --git a/config/config_helper.exs b/config/config_helper.exs index 8086279fc44e..d5f843bc3c70 100644 --- a/config/config_helper.exs +++ b/config/config_helper.exs @@ -182,4 +182,9 @@ defmodule ConfigHelper do @spec chain_type() :: String.t() def chain_type, do: System.get_env("CHAIN_TYPE") || "ethereum" + + @spec eth_call_url(String.t() | nil) :: String.t() | nil + def eth_call_url(default \\ nil) do + System.get_env("ETHEREUM_JSONRPC_ETH_CALL_URL") || System.get_env("ETHEREUM_JSONRPC_HTTP_URL") || default + end end diff --git a/config/runtime.exs b/config/runtime.exs index 5cb408608b84..0d00025dffc1 100644 --- a/config/runtime.exs +++ b/config/runtime.exs @@ -133,6 +133,10 @@ config :block_scout_web, BlockScoutWeb.Chain.Address.CoinBalance, config :block_scout_web, BlockScoutWeb.API.V2, enabled: ConfigHelper.parse_bool_env_var("API_V2_ENABLED", "true") +config :block_scout_web, BlockScoutWeb.MicroserviceInterfaces.TransactionInterpretation, + service_url: System.get_env("MICROSERVICE_TRANSACTION_INTERPRETATION_URL"), + enabled: ConfigHelper.parse_bool_env_var("MICROSERVICE_TRANSACTION_INTERPRETATION_ENABLED") + # Configures Ueberauth's Auth0 auth provider config :ueberauth, Ueberauth.Strategy.Auth0.OAuth, domain: System.get_env("ACCOUNT_AUTH0_DOMAIN"), @@ -240,6 +244,7 @@ config :explorer, Explorer.Chain.Cache.PendingBlockOperation, config :explorer, Explorer.Chain.Cache.GasPriceOracle, global_ttl: ConfigHelper.parse_time_env_var("GAS_PRICE_ORACLE_CACHE_PERIOD", "30s"), + simple_transaction_gas: ConfigHelper.parse_integer_env_var("GAS_PRICE_ORACLE_SIMPLE_TRANSACTION_GAS", 21000), num_of_blocks: ConfigHelper.parse_integer_env_var("GAS_PRICE_ORACLE_NUM_OF_BLOCKS", 200), safelow_percentile: ConfigHelper.parse_integer_env_var("GAS_PRICE_ORACLE_SAFELOW_PERCENTILE", 35), average_percentile: ConfigHelper.parse_integer_env_var("GAS_PRICE_ORACLE_AVERAGE_PERCENTILE", 60), @@ -369,6 +374,10 @@ config :explorer, Explorer.ThirdPartyIntegrations.Sourcify, chain_id: System.get_env("CHAIN_ID"), repo_url: System.get_env("SOURCIFY_REPO_URL") || "https://repo.sourcify.dev/contracts" +config :explorer, Explorer.ThirdPartyIntegrations.SolidityScan, + chain_id: System.get_env("SOLIDITYSCAN_CHAIN_ID"), + api_key: System.get_env("SOLIDITYSCAN_API_TOKEN") + enabled? = ConfigHelper.parse_bool_env_var("MICROSERVICE_SC_VERIFIER_ENABLED") # or "eth_bytecode_db" type = System.get_env("MICROSERVICE_SC_VERIFIER_TYPE", "sc_verifier") @@ -404,7 +413,9 @@ config :explorer, Explorer.Account, ], resend_interval: ConfigHelper.parse_time_env_var("ACCOUNT_VERIFICATION_EMAIL_RESEND_INTERVAL", "5m"), private_tags_limit: ConfigHelper.parse_integer_env_var("ACCOUNT_PRIVATE_TAGS_LIMIT", 2000), - watchlist_addresses_limit: ConfigHelper.parse_integer_env_var("ACCOUNT_WATCHLIST_ADDRESSES_LIMIT", 15) + watchlist_addresses_limit: ConfigHelper.parse_integer_env_var("ACCOUNT_WATCHLIST_ADDRESSES_LIMIT", 15), + notifications_limit_for_30_days: + ConfigHelper.parse_integer_env_var("ACCOUNT_WATCHLIST_NOTIFICATIONS_LIMIT_FOR_30_DAYS", 1000) config :explorer, :token_id_migration, first_block: ConfigHelper.parse_integer_env_var("TOKEN_ID_MIGRATION_FIRST_BLOCK", 0), @@ -442,18 +453,36 @@ config :explorer, Explorer.Chain.Transaction, config :explorer, Explorer.Chain.Cache.AddressesTabsCounters, ttl: ConfigHelper.parse_time_env_var("ADDRESSES_TABS_COUNTERS_TTL", "10m") +config :explorer, Explorer.MicroserviceInterfaces.BENS, + service_url: System.get_env("MICROSERVICE_BENS_URL"), + enabled: ConfigHelper.parse_bool_env_var("MICROSERVICE_BENS_ENABLED") + +config :explorer, Explorer.Migrator.TransactionsDenormalization, + batch_size: ConfigHelper.parse_integer_env_var("DENORMALIZATION_MIGRATION_BATCH_SIZE", 500), + concurrency: ConfigHelper.parse_integer_env_var("DENORMALIZATION_MIGRATION_CONCURRENCY", 10) + ############### ### Indexer ### ############### +trace_first_block = ConfigHelper.parse_integer_env_var("TRACE_FIRST_BLOCK", 0) +trace_last_block = ConfigHelper.parse_integer_or_nil_env_var("TRACE_LAST_BLOCK") + +trace_block_ranges = + case ConfigHelper.safe_get_env("TRACE_BLOCK_RANGES", nil) do + "" -> "#{trace_first_block}..#{trace_last_block || "latest"}" + ranges -> ranges + end + config :indexer, block_transformer: ConfigHelper.block_transformer(), metadata_updater_milliseconds_interval: ConfigHelper.parse_time_env_var("TOKEN_METADATA_UPDATE_INTERVAL", "48h"), block_ranges: System.get_env("BLOCK_RANGES"), first_block: ConfigHelper.parse_integer_env_var("FIRST_BLOCK", 0), last_block: ConfigHelper.parse_integer_or_nil_env_var("LAST_BLOCK"), - trace_first_block: ConfigHelper.parse_integer_env_var("TRACE_FIRST_BLOCK", 0), - trace_last_block: ConfigHelper.parse_integer_or_nil_env_var("TRACE_LAST_BLOCK"), + trace_block_ranges: trace_block_ranges, + trace_first_block: trace_first_block, + trace_last_block: trace_last_block, fetch_rewards_way: System.get_env("FETCH_REWARDS_WAY", "trace_block"), memory_limit: ConfigHelper.indexer_memory_limit(), receipts_batch_size: ConfigHelper.parse_integer_env_var("INDEXER_RECEIPTS_BATCH_SIZE", 250), @@ -570,7 +599,7 @@ config :indexer, Indexer.Fetcher.TokenInstance.Sanitize, batch_size: ConfigHelper.parse_integer_env_var("INDEXER_TOKEN_INSTANCE_SANITIZE_BATCH_SIZE", 10) config :indexer, Indexer.Fetcher.TokenInstance.LegacySanitize, - concurrency: ConfigHelper.parse_integer_env_var("INDEXER_TOKEN_INSTANCE_LEGACY_SANITIZE_CONCURRENCY", 10), + concurrency: ConfigHelper.parse_integer_env_var("INDEXER_TOKEN_INSTANCE_LEGACY_SANITIZE_CONCURRENCY", 2), batch_size: ConfigHelper.parse_integer_env_var("INDEXER_TOKEN_INSTANCE_LEGACY_SANITIZE_BATCH_SIZE", 10) config :indexer, Indexer.Fetcher.InternalTransaction, diff --git a/config/runtime/dev.exs b/config/runtime/dev.exs index c10a0b752a5c..3e4df28fb71e 100644 --- a/config/runtime/dev.exs +++ b/config/runtime/dev.exs @@ -42,12 +42,15 @@ pool_size = do: ConfigHelper.parse_integer_env_var("POOL_SIZE", 30), else: ConfigHelper.parse_integer_env_var("POOL_SIZE", 40) +queue_target = ConfigHelper.parse_integer_env_var("DATABASE_QUEUE_TARGET", 50) + # Configure your database config :explorer, Explorer.Repo, database: database, hostname: hostname, url: System.get_env("DATABASE_URL"), - pool_size: pool_size + pool_size: pool_size, + queue_target: queue_target database_api = if System.get_env("DATABASE_READ_ONLY_API_URL"), do: nil, else: database hostname_api = if System.get_env("DATABASE_READ_ONLY_API_URL"), do: nil, else: hostname @@ -57,7 +60,8 @@ config :explorer, Explorer.Repo.Replica1, database: database_api, hostname: hostname_api, url: ExplorerConfigHelper.get_api_db_url(), - pool_size: ConfigHelper.parse_integer_env_var("POOL_SIZE_API", 10) + pool_size: ConfigHelper.parse_integer_env_var("POOL_SIZE_API", 10), + queue_target: queue_target database_account = if System.get_env("ACCOUNT_DATABASE_URL"), do: nil, else: database hostname_account = if System.get_env("ACCOUNT_DATABASE_URL"), do: nil, else: hostname @@ -67,7 +71,8 @@ config :explorer, Explorer.Repo.Account, database: database_account, hostname: hostname_account, url: ExplorerConfigHelper.get_account_db_url(), - pool_size: ConfigHelper.parse_integer_env_var("ACCOUNT_POOL_SIZE", 10) + pool_size: ConfigHelper.parse_integer_env_var("ACCOUNT_POOL_SIZE", 10), + queue_target: queue_target # Configure PolygonEdge database config :explorer, Explorer.Repo.PolygonEdge, diff --git a/config/runtime/prod.exs b/config/runtime/prod.exs index a8692d8c540e..ce4602522a1d 100644 --- a/config/runtime/prod.exs +++ b/config/runtime/prod.exs @@ -28,24 +28,28 @@ config :block_scout_web, BlockScoutWeb.Endpoint, ################ pool_size = ConfigHelper.parse_integer_env_var("POOL_SIZE", 50) +queue_target = ConfigHelper.parse_integer_env_var("DATABASE_QUEUE_TARGET", 50) # Configures the database config :explorer, Explorer.Repo, url: System.get_env("DATABASE_URL"), pool_size: pool_size, - ssl: ExplorerConfigHelper.ssl_enabled?() + ssl: ExplorerConfigHelper.ssl_enabled?(), + queue_target: queue_target # Configures API the database config :explorer, Explorer.Repo.Replica1, url: ExplorerConfigHelper.get_api_db_url(), pool_size: ConfigHelper.parse_integer_env_var("POOL_SIZE_API", 50), - ssl: ExplorerConfigHelper.ssl_enabled?() + ssl: ExplorerConfigHelper.ssl_enabled?(), + queue_target: queue_target # Configures Account database config :explorer, Explorer.Repo.Account, url: ExplorerConfigHelper.get_account_db_url(), pool_size: ConfigHelper.parse_integer_env_var("ACCOUNT_POOL_SIZE", 50), - ssl: ExplorerConfigHelper.ssl_enabled?() + ssl: ExplorerConfigHelper.ssl_enabled?(), + queue_target: queue_target # Configures PolygonEdge database config :explorer, Explorer.Repo.PolygonEdge, diff --git a/cspell.json b/cspell.json index 95077caf8ceb..d19d333d4403 100644 --- a/cspell.json +++ b/cspell.json @@ -118,6 +118,7 @@ "decompiler", "Decompiler", "dedup", + "DefiLlama", "defmock", "defsupervisor", "dejob", @@ -126,6 +127,8 @@ "DELEGATECALL", "delegators", "demonitor", + "denormalization", + "Denormalization", "Denormalized", "descr", "describedby", @@ -165,6 +168,7 @@ "Faileddi", "falala", "Filesize", + "fkey", "Floki", "fontawesome", "fortawesome", @@ -232,6 +236,7 @@ "kittencream", "labeledby", "labelledby", + "lastmod", "lastname", "lastword", "lformat", @@ -256,11 +261,13 @@ "mconst", "mdef", "MDWW", + "meer", "Mendonça", "Menlo", "mergeable", "Merkle", "metatags", + "microsecs", "millis", "mintings", "mistmatches", @@ -316,6 +323,7 @@ "onconnect", "ondisconnect", "outcoming", + "overengineering", "pawesome", "pbcopy", "peeker", @@ -351,6 +359,7 @@ "purrstige", "qdai", "Qebz", + "qitmeer", "Qmbgk", "qrcode", "queriable", @@ -403,6 +412,7 @@ "snapshotted", "snapshotting", "Sokol", + "SOLIDITYSCAN", "soljson", "someout", "sourcecode", @@ -490,6 +500,7 @@ "upserting", "upserts", "urijs", + "urlset", "Utqn", "UUPS", "valign", @@ -528,17 +539,7 @@ "zindex", "zipcode", "zkbob", - "zkevm", - "erts", - "Asfpp", - "Nerg", - "secp", - "qwertyuioiuytrewertyuioiuytrertyuio", - "urlset", - "lastmod", - "qitmeer", - "meer", - "DefiLlama" + "zkevm" ], "enableFiletypes": [ "dotenv", diff --git a/docker-compose/README.md b/docker-compose/README.md index c212e1cde760..cc472ebd4c40 100644 --- a/docker-compose/README.md +++ b/docker-compose/README.md @@ -39,15 +39,19 @@ The repo contains built-in configs for different JSON RPC clients without need t **Note**: in all below examples, you can use `docker compose` instead of `docker-compose`, if compose v2 plugin is installed in Docker. -- Erigon: `docker-compose -f docker-compose-no-build-erigon.yml up -d` -- Geth (suitable for Reth as well): `docker-compose -f docker-compose-no-build-geth.yml up -d` -- Geth Clique: `docker-compose -f docker-compose-no-build-geth-clique-consensus.yml up -d` -- Nethermind, OpenEthereum: `docker-compose -f docker-compose-no-build-nethermind up -d` -- Ganache: `docker-compose -f docker-compose-no-build-ganache.yml up -d` -- HardHat network: `docker-compose -f docker-compose-no-build-hardhat-network.yml up -d` -- Running only explorer without DB: `docker-compose -f docker-compose-no-build-no-db-container.yml up -d`. In this case, one container is created - for the explorer itself. And it assumes that the DB credentials are provided through `DATABASE_URL` environment variable. -- Running explorer with external backend: `docker-compose -f docker-compose-no-build-external-backend.yml up -d` -- Running explorer with external frontend: `docker-compose -f docker-compose-no-build-external-frontend.yml up -d` +| __JSON RPC Client__ | __Docker compose launch command__ | +| -------- | ------- | +| Erigon | `docker-compose -f erigon.yml up -d` | +| Geth (suitable for Reth as well) | `docker-compose -f geth.yml up -d` | +| Geth Clique | `docker-compose -f geth-clique-consensus.yml up -d` | +| Nethermind, OpenEthereum | `docker-compose -f nethermind up -d` | +| Ganache | `docker-compose -f ganache.yml up -d` | +| HardHat network | `docker-compose -f hardhat-network.yml up -d` | + +- Running only explorer without DB: `docker-compose -f external-db.yml up -d`. In this case, no db container is created. And it assumes that the DB credentials are provided through `DATABASE_URL` environment variable on the backend container. +- Running explorer with external backend: `docker-compose -f external-backend.yml up -d` +- Running explorer with external frontend: `docker-compose -f external-frontend.yml up -d` +- Running all microservices: `docker-compose -f microservices.yml up -d` All of the configs assume the Ethereum JSON RPC is running at http://localhost:8545. diff --git a/docker-compose/docker-compose-no-build-nethermind.yml b/docker-compose/docker-compose-no-build-nethermind.yml deleted file mode 100644 index 77c5e5eef9b7..000000000000 --- a/docker-compose/docker-compose-no-build-nethermind.yml +++ /dev/null @@ -1,74 +0,0 @@ -version: '3.9' - -services: - redis_db: - extends: - file: ./services/docker-compose-redis.yml - service: redis_db - - db-init: - extends: - file: ./services/docker-compose-db.yml - service: db-init - - db: - extends: - file: ./services/docker-compose-db.yml - service: db - - backend: - depends_on: - - db - - redis_db - extends: - file: ./services/docker-compose-backend.yml - service: backend - links: - - db:database - environment: - ETHEREUM_JSONRPC_VARIANT: 'nethermind' - - visualizer: - extends: - file: ./services/docker-compose-visualizer.yml - service: visualizer - - sig-provider: - extends: - file: ./services/docker-compose-sig-provider.yml - service: sig-provider - - frontend: - depends_on: - - backend - extends: - file: ./services/docker-compose-frontend.yml - service: frontend - - stats-db-init: - extends: - file: ./services/docker-compose-stats.yml - service: stats-db-init - - stats-db: - depends_on: - - backend - extends: - file: ./services/docker-compose-stats.yml - service: stats-db - - stats: - depends_on: - - stats-db - extends: - file: ./services/docker-compose-stats.yml - service: stats - - proxy: - depends_on: - - backend - - frontend - - stats - extends: - file: ./services/docker-compose-nginx.yml - service: proxy diff --git a/docker-compose/docker-compose.yml b/docker-compose/docker-compose.yml index 0ce792f80ded..b00502a5a187 100644 --- a/docker-compose/docker-compose.yml +++ b/docker-compose/docker-compose.yml @@ -3,17 +3,17 @@ version: '3.9' services: redis_db: extends: - file: ./services/docker-compose-redis.yml + file: ./services/redis.yml service: redis_db db-init: extends: - file: ./services/docker-compose-db.yml + file: ./services/db.yml service: db-init db: extends: - file: ./services/docker-compose-db.yml + file: ./services/db.yml service: db backend: @@ -21,7 +21,7 @@ services: - db - redis_db extends: - file: ./services/docker-compose-backend.yml + file: ./services/backend.yml service: backend build: context: .. @@ -34,7 +34,7 @@ services: CACHE_TOTAL_GAS_USAGE_COUNTER_ENABLED: "" CACHE_ADDRESS_WITH_BALANCES_UPDATE_INTERVAL: "" ADMIN_PANEL_ENABLED: "" - RELEASE_VERSION: 5.3.2 + RELEASE_VERSION: 6.0.0 links: - db:database environment: @@ -45,38 +45,38 @@ services: visualizer: extends: - file: ./services/docker-compose-visualizer.yml + file: ./services/visualizer.yml service: visualizer sig-provider: extends: - file: ./services/docker-compose-sig-provider.yml + file: ./services/sig-provider.yml service: sig-provider frontend: depends_on: - backend extends: - file: ./services/docker-compose-frontend.yml + file: ./services/frontend.yml service: frontend stats-db-init: extends: - file: ./services/docker-compose-stats.yml + file: ./services/stats.yml service: stats-db-init stats-db: depends_on: - backend extends: - file: ./services/docker-compose-stats.yml + file: ./services/stats.yml service: stats-db stats: depends_on: - stats-db extends: - file: ./services/docker-compose-stats.yml + file: ./services/stats.yml service: stats proxy: @@ -85,5 +85,5 @@ services: - frontend - stats extends: - file: ./services/docker-compose-nginx.yml + file: ./services/nginx.yml service: proxy diff --git a/docker-compose/envs/common-blockscout.env b/docker-compose/envs/common-blockscout.env index 8ea2e9265dbe..f1743f72265e 100644 --- a/docker-compose/envs/common-blockscout.env +++ b/docker-compose/envs/common-blockscout.env @@ -3,8 +3,10 @@ ETHEREUM_JSONRPC_VARIANT=geth ETHEREUM_JSONRPC_HTTP_URL=http://host.docker.internal:8545/ # ETHEREUM_JSONRPC_FALLBACK_HTTP_URL= DATABASE_URL=postgresql://blockscout:ceWb1MeLBEeOIfk65gU8EjF8@db:5432/blockscout +# DATABASE_QUEUE_TARGET ETHEREUM_JSONRPC_TRACE_URL=http://host.docker.internal:8545/ # ETHEREUM_JSONRPC_FALLBACK_TRACE_URL= +# ETHEREUM_JSONRPC_ETH_CALL_URL= # ETHEREUM_JSONRPC_HTTP_TIMEOUT= # CHAIN_TYPE= NETWORK= @@ -62,6 +64,7 @@ BLOCK_TRANSFORMER=base # BLOCK_RANGES= # FIRST_BLOCK= # LAST_BLOCK= +# TRACE_BLOCK_RANGES= # TRACE_FIRST_BLOCK= # TRACE_LAST_BLOCK= # FOOTER_CHAT_LINK= @@ -180,6 +183,12 @@ COIN_BALANCE_HISTORY_DAYS=90 APPS_MENU=true EXTERNAL_APPS=[] # GAS_PRICE= +# GAS_PRICE_ORACLE_CACHE_PERIOD= +# GAS_PRICE_ORACLE_SIMPLE_TRANSACTION_GAS= +# GAS_PRICE_ORACLE_NUM_OF_BLOCKS= +# GAS_PRICE_ORACLE_SAFELOW_PERCENTILE= +# GAS_PRICE_ORACLE_AVERAGE_PERCENTILE= +# GAS_PRICE_ORACLE_FAST_PERCENTILE= # RESTRICTED_LIST= # RESTRICTED_LIST_KEY= SHOW_MAINTENANCE_ALERT=false @@ -257,3 +266,7 @@ API_V2_ENABLED=true # ADDRESSES_TABS_COUNTERS_TTL=10m # ACCOUNT_PRIVATE_TAGS_LIMIT=2000 # ACCOUNT_WATCHLIST_ADDRESSES_LIMIT=15 +# MICROSERVICE_BENS_URL= +# MICROSERVICE_BENS_ENABLED= +# DENORMALIZATION_MIGRATION_BATCH_SIZE= +# DENORMALIZATION_MIGRATION_CONCURRENCY= diff --git a/docker-compose/docker-compose-no-build-erigon.yml b/docker-compose/erigon.yml similarity index 61% rename from docker-compose/docker-compose-no-build-erigon.yml rename to docker-compose/erigon.yml index 44e8f0441b78..0dbcef7f3e0e 100644 --- a/docker-compose/docker-compose-no-build-erigon.yml +++ b/docker-compose/erigon.yml @@ -3,17 +3,17 @@ version: '3.9' services: redis_db: extends: - file: ./services/docker-compose-redis.yml + file: ./services/redis.yml service: redis_db db-init: extends: - file: ./services/docker-compose-db.yml + file: ./services/db.yml service: db-init db: extends: - file: ./services/docker-compose-db.yml + file: ./services/db.yml service: db backend: @@ -21,7 +21,7 @@ services: - db - redis_db extends: - file: ./services/docker-compose-backend.yml + file: ./services/backend.yml service: backend links: - db:database @@ -30,38 +30,38 @@ services: visualizer: extends: - file: ./services/docker-compose-visualizer.yml + file: ./services/visualizer.yml service: visualizer sig-provider: extends: - file: ./services/docker-compose-sig-provider.yml + file: ./services/sig-provider.yml service: sig-provider frontend: depends_on: - backend extends: - file: ./services/docker-compose-frontend.yml + file: ./services/frontend.yml service: frontend stats-db-init: extends: - file: ./services/docker-compose-stats.yml + file: ./services/stats.yml service: stats-db-init stats-db: depends_on: - backend extends: - file: ./services/docker-compose-stats.yml + file: ./services/stats.yml service: stats-db stats: depends_on: - stats-db extends: - file: ./services/docker-compose-stats.yml + file: ./services/stats.yml service: stats proxy: @@ -70,5 +70,5 @@ services: - frontend - stats extends: - file: ./services/docker-compose-nginx.yml + file: ./services/nginx.yml service: proxy diff --git a/docker-compose/docker-compose-no-build-external-backend.yml b/docker-compose/external-backend.yml similarity index 55% rename from docker-compose/docker-compose-no-build-external-backend.yml rename to docker-compose/external-backend.yml index bd6acb24d8ee..db8df3758037 100644 --- a/docker-compose/docker-compose-no-build-external-backend.yml +++ b/docker-compose/external-backend.yml @@ -3,49 +3,49 @@ version: '3.9' services: redis_db: extends: - file: ./services/docker-compose-redis.yml + file: ./services/redis.yml service: redis_db db-init: extends: - file: ./services/docker-compose-db.yml + file: ./services/db.yml service: db-init db: extends: - file: ./services/docker-compose-db.yml + file: ./services/db.yml service: db visualizer: extends: - file: ./services/docker-compose-visualizer.yml + file: ./services/visualizer.yml service: visualizer sig-provider: extends: - file: ./services/docker-compose-sig-provider.yml + file: ./services/sig-provider.yml service: sig-provider frontend: extends: - file: ./services/docker-compose-frontend.yml + file: ./services/frontend.yml service: frontend stats-db-init: extends: - file: ./services/docker-compose-stats.yml + file: ./services/stats.yml service: stats-db-init stats-db: extends: - file: ./services/docker-compose-stats.yml + file: ./services/stats.yml service: stats-db stats: depends_on: - stats-db extends: - file: ./services/docker-compose-stats.yml + file: ./services/stats.yml service: stats proxy: @@ -53,5 +53,5 @@ services: - frontend - stats extends: - file: ./services/docker-compose-nginx.yml + file: ./services/nginx.yml service: proxy diff --git a/docker-compose/docker-compose-no-build-no-db-container.yml b/docker-compose/external-db.yml similarity index 62% rename from docker-compose/docker-compose-no-build-no-db-container.yml rename to docker-compose/external-db.yml index a9a95c458b88..b40151c51dec 100644 --- a/docker-compose/docker-compose-no-build-no-db-container.yml +++ b/docker-compose/external-db.yml @@ -3,52 +3,52 @@ version: '3.9' services: redis_db: extends: - file: ./services/docker-compose-redis.yml + file: ./services/redis.yml service: redis_db backend: depends_on: - redis_db extends: - file: ./services/docker-compose-backend.yml + file: ./services/backend.yml service: backend environment: ETHEREUM_JSONRPC_VARIANT: 'geth' visualizer: extends: - file: ./services/docker-compose-visualizer.yml + file: ./services/visualizer.yml service: visualizer sig-provider: extends: - file: ./services/docker-compose-sig-provider.yml + file: ./services/sig-provider.yml service: sig-provider frontend: depends_on: - backend extends: - file: ./services/docker-compose-frontend.yml + file: ./services/frontend.yml service: frontend stats-db-init: extends: - file: ./services/docker-compose-stats.yml + file: ./services/stats.yml service: stats-db-init stats-db: depends_on: - backend extends: - file: ./services/docker-compose-stats.yml + file: ./services/stats.yml service: stats-db stats: depends_on: - stats-db extends: - file: ./services/docker-compose-stats.yml + file: ./services/stats.yml service: stats proxy: @@ -57,5 +57,5 @@ services: - frontend - stats extends: - file: ./services/docker-compose-nginx.yml + file: ./services/nginx.yml service: proxy diff --git a/docker-compose/docker-compose-no-build-external-frontend.yml b/docker-compose/external-frontend.yml similarity index 66% rename from docker-compose/docker-compose-no-build-external-frontend.yml rename to docker-compose/external-frontend.yml index 71804ffeae73..f0d9f9f89298 100644 --- a/docker-compose/docker-compose-no-build-external-frontend.yml +++ b/docker-compose/external-frontend.yml @@ -3,17 +3,17 @@ version: '3.9' services: redis_db: extends: - file: ./services/docker-compose-redis.yml + file: ./services/redis.yml service: redis_db db-init: extends: - file: ./services/docker-compose-db.yml + file: ./services/db.yml service: db-init db: extends: - file: ./services/docker-compose-db.yml + file: ./services/db.yml service: db backend: @@ -21,7 +21,7 @@ services: - db - redis_db extends: - file: ./services/docker-compose-backend.yml + file: ./services/backend.yml service: backend links: - db:database @@ -34,31 +34,31 @@ services: visualizer: extends: - file: ./services/docker-compose-visualizer.yml + file: ./services/visualizer.yml service: visualizer sig-provider: extends: - file: ./services/docker-compose-sig-provider.yml + file: ./services/sig-provider.yml service: sig-provider stats-db-init: extends: - file: ./services/docker-compose-stats.yml + file: ./services/stats.yml service: stats-db-init stats-db: depends_on: - backend extends: - file: ./services/docker-compose-stats.yml + file: ./services/stats.yml service: stats-db stats: depends_on: - stats-db extends: - file: ./services/docker-compose-stats.yml + file: ./services/stats.yml service: stats proxy: @@ -66,5 +66,5 @@ services: - backend - stats extends: - file: ./services/docker-compose-nginx.yml + file: ./services/nginx.yml service: proxy \ No newline at end of file diff --git a/docker-compose/docker-compose-no-build-ganache.yml b/docker-compose/ganache.yml similarity index 66% rename from docker-compose/docker-compose-no-build-ganache.yml rename to docker-compose/ganache.yml index f86be323868b..0aa51fce16a2 100644 --- a/docker-compose/docker-compose-no-build-ganache.yml +++ b/docker-compose/ganache.yml @@ -3,17 +3,17 @@ version: '3.9' services: redis_db: extends: - file: ./services/docker-compose-redis.yml + file: ./services/redis.yml service: redis_db db-init: extends: - file: ./services/docker-compose-db.yml + file: ./services/db.yml service: db-init db: extends: - file: ./services/docker-compose-db.yml + file: ./services/db.yml service: db backend: @@ -21,7 +21,7 @@ services: - db - redis_db extends: - file: ./services/docker-compose-backend.yml + file: ./services/backend.yml service: backend links: - db:database @@ -34,38 +34,38 @@ services: visualizer: extends: - file: ./services/docker-compose-visualizer.yml + file: ./services/visualizer.yml service: visualizer sig-provider: extends: - file: ./services/docker-compose-sig-provider.yml + file: ./services/sig-provider.yml service: sig-provider frontend: depends_on: - backend extends: - file: ./services/docker-compose-frontend.yml + file: ./services/frontend.yml service: frontend stats-db-init: extends: - file: ./services/docker-compose-stats.yml + file: ./services/stats.yml service: stats-db-init stats-db: depends_on: - backend extends: - file: ./services/docker-compose-stats.yml + file: ./services/stats.yml service: stats-db stats: depends_on: - stats-db extends: - file: ./services/docker-compose-stats.yml + file: ./services/stats.yml service: stats proxy: @@ -74,5 +74,5 @@ services: - frontend - stats extends: - file: ./services/docker-compose-nginx.yml + file: ./services/nginx.yml service: proxy diff --git a/docker-compose/docker-compose-no-build-geth-clique-consensus.yml b/docker-compose/geth-clique-consensus.yml similarity index 62% rename from docker-compose/docker-compose-no-build-geth-clique-consensus.yml rename to docker-compose/geth-clique-consensus.yml index 4f605567e431..ac1573849204 100644 --- a/docker-compose/docker-compose-no-build-geth-clique-consensus.yml +++ b/docker-compose/geth-clique-consensus.yml @@ -3,17 +3,17 @@ version: '3.9' services: redis_db: extends: - file: ./services/docker-compose-redis.yml + file: ./services/redis.yml service: redis_db db-init: extends: - file: ./services/docker-compose-db.yml + file: ./services/db.yml service: db-init db: extends: - file: ./services/docker-compose-db.yml + file: ./services/db.yml service: db backend: @@ -21,7 +21,7 @@ services: - db - redis_db extends: - file: ./services/docker-compose-backend.yml + file: ./services/backend.yml service: backend links: - db:database @@ -31,38 +31,38 @@ services: visualizer: extends: - file: ./services/docker-compose-visualizer.yml + file: ./services/visualizer.yml service: visualizer sig-provider: extends: - file: ./services/docker-compose-sig-provider.yml + file: ./services/sig-provider.yml service: sig-provider frontend: depends_on: - backend extends: - file: ./services/docker-compose-frontend.yml + file: ./services/frontend.yml service: frontend stats-db-init: extends: - file: ./services/docker-compose-stats.yml + file: ./services/stats.yml service: stats-db-init stats-db: depends_on: - backend extends: - file: ./services/docker-compose-stats.yml + file: ./services/stats.yml service: stats-db stats: depends_on: - stats-db extends: - file: ./services/docker-compose-stats.yml + file: ./services/stats.yml service: stats proxy: @@ -71,5 +71,5 @@ services: - frontend - stats extends: - file: ./services/docker-compose-nginx.yml + file: ./services/nginx.yml service: proxy diff --git a/docker-compose/docker-compose-no-build-geth.yml b/docker-compose/geth.yml similarity index 61% rename from docker-compose/docker-compose-no-build-geth.yml rename to docker-compose/geth.yml index 94ccea912a2a..611acec30606 100644 --- a/docker-compose/docker-compose-no-build-geth.yml +++ b/docker-compose/geth.yml @@ -3,17 +3,17 @@ version: '3.9' services: redis_db: extends: - file: ./services/docker-compose-redis.yml + file: ./services/redis.yml service: redis_db db-init: extends: - file: ./services/docker-compose-db.yml + file: ./services/db.yml service: db-init db: extends: - file: ./services/docker-compose-db.yml + file: ./services/db.yml service: db backend: @@ -21,7 +21,7 @@ services: - db - redis_db extends: - file: ./services/docker-compose-backend.yml + file: ./services/backend.yml service: backend links: - db:database @@ -30,38 +30,38 @@ services: visualizer: extends: - file: ./services/docker-compose-visualizer.yml + file: ./services/visualizer.yml service: visualizer sig-provider: extends: - file: ./services/docker-compose-sig-provider.yml + file: ./services/sig-provider.yml service: sig-provider frontend: depends_on: - backend extends: - file: ./services/docker-compose-frontend.yml + file: ./services/frontend.yml service: frontend stats-db-init: extends: - file: ./services/docker-compose-stats.yml + file: ./services/stats.yml service: stats-db-init stats-db: depends_on: - backend extends: - file: ./services/docker-compose-stats.yml + file: ./services/stats.yml service: stats-db stats: depends_on: - stats-db extends: - file: ./services/docker-compose-stats.yml + file: ./services/stats.yml service: stats proxy: @@ -70,5 +70,5 @@ services: - frontend - stats extends: - file: ./services/docker-compose-nginx.yml + file: ./services/nginx.yml service: proxy diff --git a/docker-compose/docker-compose-no-build-hardhat-network.yml b/docker-compose/hardhat-network.yml similarity index 64% rename from docker-compose/docker-compose-no-build-hardhat-network.yml rename to docker-compose/hardhat-network.yml index defe8e7b35ff..518a3b2af73b 100644 --- a/docker-compose/docker-compose-no-build-hardhat-network.yml +++ b/docker-compose/hardhat-network.yml @@ -3,17 +3,17 @@ version: '3.9' services: redis_db: extends: - file: ./services/docker-compose-redis.yml + file: ./services/redis.yml service: redis_db db-init: extends: - file: ./services/docker-compose-db.yml + file: ./services/db.yml service: db-init db: extends: - file: ./services/docker-compose-db.yml + file: ./services/db.yml service: db backend: @@ -21,7 +21,7 @@ services: - db - redis_db extends: - file: ./services/docker-compose-backend.yml + file: ./services/backend.yml service: backend links: - db:database @@ -32,38 +32,38 @@ services: visualizer: extends: - file: ./services/docker-compose-visualizer.yml + file: ./services/visualizer.yml service: visualizer sig-provider: extends: - file: ./services/docker-compose-sig-provider.yml + file: ./services/sig-provider.yml service: sig-provider frontend: depends_on: - backend extends: - file: ./services/docker-compose-frontend.yml + file: ./services/frontend.yml service: frontend stats-db-init: extends: - file: ./services/docker-compose-stats.yml + file: ./services/stats.yml service: stats-db-init stats-db: depends_on: - backend extends: - file: ./services/docker-compose-stats.yml + file: ./services/stats.yml service: stats-db stats: depends_on: - stats-db extends: - file: ./services/docker-compose-stats.yml + file: ./services/stats.yml service: stats proxy: @@ -72,5 +72,5 @@ services: - frontend - stats extends: - file: ./services/docker-compose-nginx.yml + file: ./services/nginx.yml service: proxy diff --git a/docker-compose/microservices.yml b/docker-compose/microservices.yml new file mode 100644 index 000000000000..ee4a07bd6d1d --- /dev/null +++ b/docker-compose/microservices.yml @@ -0,0 +1,49 @@ +version: '3.9' + +services: + visualizer: + extends: + file: ./services/visualizer.yml + service: visualizer + + sig-provider: + extends: + file: ./services/sig-provider.yml + service: sig-provider + ports: + - 8083:8050 + + + sc-verifier: + extends: + file: ./services/smart-contract-verifier.yml + service: smart-contract-verifier + ports: + - 8082:8050 + + stats-db-init: + extends: + file: ./services/stats.yml + service: stats-db-init + + stats-db: + extends: + file: ./services/stats.yml + service: stats-db + + stats: + depends_on: + - stats-db + extends: + file: ./services/stats.yml + service: stats + + proxy: + depends_on: + - visualizer + - stats + extends: + file: ./services/nginx.yml + service: proxy + volumes: + - "./proxy/microservices.conf.template:/etc/nginx/templates/default.conf.template" diff --git a/docker-compose/proxy/microservices.conf.template b/docker-compose/proxy/microservices.conf.template new file mode 100644 index 000000000000..708812f57113 --- /dev/null +++ b/docker-compose/proxy/microservices.conf.template @@ -0,0 +1,65 @@ +map $http_upgrade $connection_upgrade { + + default upgrade; + '' close; +} + +server { + listen 8080; + server_name localhost; + proxy_http_version 1.1; + proxy_hide_header Access-Control-Allow-Origin; + proxy_hide_header Access-Control-Allow-Methods; + add_header 'Access-Control-Allow-Origin' 'http://localhost:3000' always; + add_header 'Access-Control-Allow-Credentials' 'true' always; + add_header 'Access-Control-Allow-Methods' 'PUT, GET, POST, OPTIONS, DELETE, PATCH' always; + + location / { + proxy_pass http://stats:8050/; + proxy_http_version 1.1; + proxy_set_header Host "$host"; + proxy_set_header X-Real-IP "$remote_addr"; + proxy_set_header X-Forwarded-For "$proxy_add_x_forwarded_for"; + proxy_set_header X-Forwarded-Proto "$scheme"; + proxy_set_header Upgrade "$http_upgrade"; + proxy_set_header Connection $connection_upgrade; + proxy_cache_bypass $http_upgrade; + } +} +server { + listen 8081; + server_name localhost; + proxy_http_version 1.1; + proxy_hide_header Access-Control-Allow-Origin; + proxy_hide_header Access-Control-Allow-Methods; + add_header 'Access-Control-Allow-Origin' 'http://localhost:3000' always; + add_header 'Access-Control-Allow-Credentials' 'true' always; + add_header 'Access-Control-Allow-Methods' 'PUT, GET, POST, OPTIONS, DELETE, PATCH' always; + add_header 'Access-Control-Allow-Headers' 'DNT,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Authorization,x-csrf-token' always; + + location / { + proxy_pass http://visualizer:8050/; + proxy_http_version 1.1; + proxy_buffering off; + proxy_set_header Host "$host"; + proxy_set_header X-Real-IP "$remote_addr"; + proxy_connect_timeout 30m; + proxy_read_timeout 30m; + proxy_send_timeout 30m; + proxy_set_header X-Forwarded-For "$proxy_add_x_forwarded_for"; + proxy_set_header X-Forwarded-Proto "$scheme"; + proxy_set_header Upgrade "$http_upgrade"; + proxy_set_header Connection $connection_upgrade; + proxy_cache_bypass $http_upgrade; + if ($request_method = 'OPTIONS') { + add_header 'Access-Control-Allow-Origin' 'http://localhost:3000' always; + add_header 'Access-Control-Allow-Credentials' 'true' always; + add_header 'Access-Control-Allow-Methods' 'PUT, GET, POST, OPTIONS, DELETE, PATCH' always; + add_header 'Access-Control-Allow-Headers' 'DNT,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Authorization,x-csrf-token' always; + add_header 'Access-Control-Max-Age' 1728000; + add_header 'Content-Type' 'text/plain charset=UTF-8'; + add_header 'Content-Length' 0; + return 204; + } + } +} \ No newline at end of file diff --git a/docker-compose/services/docker-compose-backend.yml b/docker-compose/services/backend.yml similarity index 85% rename from docker-compose/services/docker-compose-backend.yml rename to docker-compose/services/backend.yml index d5f934fb653f..cf8a0871d366 100644 --- a/docker-compose/services/docker-compose-backend.yml +++ b/docker-compose/services/backend.yml @@ -2,7 +2,7 @@ version: '3.9' services: backend: - image: blockscout/blockscout:${DOCKER_TAG:-latest} + image: blockscout/${DOCKER_REPO:-blockscout}:${DOCKER_TAG:-latest} pull_policy: always restart: always stop_grace_period: 5m diff --git a/docker-compose/services/docker-compose-db.yml b/docker-compose/services/db.yml similarity index 89% rename from docker-compose/services/docker-compose-db.yml rename to docker-compose/services/db.yml index 131d90bb931c..b7d036e966b2 100644 --- a/docker-compose/services/docker-compose-db.yml +++ b/docker-compose/services/db.yml @@ -19,7 +19,7 @@ services: user: 2000:2000 restart: always container_name: 'db' - command: postgres -c 'max_connections=200' + command: postgres -c 'max_connections=200' -c 'client_connection_check_interval=60000' environment: POSTGRES_DB: 'blockscout' POSTGRES_USER: 'blockscout' diff --git a/docker-compose/services/docker-compose-frontend.yml b/docker-compose/services/frontend.yml similarity index 100% rename from docker-compose/services/docker-compose-frontend.yml rename to docker-compose/services/frontend.yml diff --git a/docker-compose/services/docker-compose-nginx.yml b/docker-compose/services/nginx.yml similarity index 100% rename from docker-compose/services/docker-compose-nginx.yml rename to docker-compose/services/nginx.yml diff --git a/docker-compose/services/docker-compose-redis.yml b/docker-compose/services/redis.yml similarity index 100% rename from docker-compose/services/docker-compose-redis.yml rename to docker-compose/services/redis.yml diff --git a/docker-compose/services/docker-compose-sig-provider.yml b/docker-compose/services/sig-provider.yml similarity index 100% rename from docker-compose/services/docker-compose-sig-provider.yml rename to docker-compose/services/sig-provider.yml diff --git a/docker-compose/services/docker-compose-smart-contract-verifier.yml b/docker-compose/services/smart-contract-verifier.yml similarity index 100% rename from docker-compose/services/docker-compose-smart-contract-verifier.yml rename to docker-compose/services/smart-contract-verifier.yml diff --git a/docker-compose/services/docker-compose-stats.yml b/docker-compose/services/stats.yml similarity index 91% rename from docker-compose/services/docker-compose-stats.yml rename to docker-compose/services/stats.yml index 5077a5d893c2..0ba073432d33 100644 --- a/docker-compose/services/docker-compose-stats.yml +++ b/docker-compose/services/stats.yml @@ -49,6 +49,6 @@ services: - ../envs/common-stats.env environment: - STATS__DB_URL=postgres://stats:n0uejXPl61ci6ldCuE2gQU5Y@stats-db:5432/stats - - STATS__BLOCKSCOUT_DB_URL=postgresql://blockscout:ceWb1MeLBEeOIfk65gU8EjF8@db:5432/blockscout + - STATS__BLOCKSCOUT_DB_URL=${STATS__BLOCKSCOUT_DB_URL-postgresql://blockscout:ceWb1MeLBEeOIfk65gU8EjF8@db:5432/blockscout} - STATS__CREATE_DATABASE=true - STATS__RUN_MIGRATIONS=true diff --git a/docker-compose/services/docker-compose-visualizer.yml b/docker-compose/services/visualizer.yml similarity index 100% rename from docker-compose/services/docker-compose-visualizer.yml rename to docker-compose/services/visualizer.yml diff --git a/docker/Makefile b/docker/Makefile index e320e4c98356..63bcd32de63b 100644 --- a/docker/Makefile +++ b/docker/Makefile @@ -10,25 +10,25 @@ STATS_CONTAINER_NAME := stats STATS_DB_CONTAINER_NAME := stats-postgres PROXY_CONTAINER_NAME := proxy PG_CONTAINER_NAME := postgres -RELEASE_VERSION ?= '5.3.2' +RELEASE_VERSION ?= '6.0.0' TAG := $(RELEASE_VERSION)-commit-$(shell git log -1 --pretty=format:"%h") STABLE_TAG := $(RELEASE_VERSION) start: @echo "==> Starting blockscout db" - @docker-compose -f ../docker-compose/services/docker-compose-db.yml up -d + @docker-compose -f ../docker-compose/services/db.yml up -d @echo "==> Starting blockscout backend" - @docker-compose -f ../docker-compose/services/docker-compose-backend.yml up -d + @docker-compose -f ../docker-compose/services/backend.yml up -d @echo "==> Starting stats microservice" - @docker-compose -f ../docker-compose/services/docker-compose-stats.yml up -d + @docker-compose -f ../docker-compose/services/stats.yml up -d @echo "==> Starting visualizer microservice" - @docker-compose -f ../docker-compose/services/docker-compose-visualizer.yml up -d + @docker-compose -f ../docker-compose/services/visualizer.yml up -d @echo "==> Starting sig-provider microservice" - @docker-compose -f ../docker-compose/services/docker-compose-sig-provider.yml up -d + @docker-compose -f ../docker-compose/services/sig-provider.yml up -d @echo "==> Starting blockscout frontend" - @docker-compose -f ../docker-compose/services/docker-compose-frontend.yml up -d + @docker-compose -f ../docker-compose/services/frontend.yml up -d @echo "==> Starting Nginx proxy" - @docker-compose -f ../docker-compose/services/docker-compose-nginx.yml up -d + @docker-compose -f ../docker-compose/services/nginx.yml up -d BS_BACKEND_STARTED := $(shell docker ps --no-trunc --filter name=^/${BACKEND_CONTAINER_NAME}$ | grep ${BACKEND_CONTAINER_NAME}) BS_FRONTEND_STARTED := $(shell docker ps --no-trunc --filter name=^/${FRONTEND_CONTAINER_NAME}$ | grep ${FRONTEND_CONTAINER_NAME}) diff --git a/mix.exs b/mix.exs index f3a4838a8b3d..06f570b70e79 100644 --- a/mix.exs +++ b/mix.exs @@ -7,7 +7,7 @@ defmodule BlockScout.Mixfile do [ # app: :block_scout, # aliases: aliases(config_env()), - version: "5.3.2", + version: "6.0.0", apps_path: "apps", deps: deps(), dialyzer: dialyzer(), @@ -96,7 +96,7 @@ defmodule BlockScout.Mixfile do {:absinthe_plug, git: "https://github.com/blockscout/absinthe_plug.git", tag: "1.5.3", override: true}, {:tesla, "~> 1.8.0"}, # Documentation - {:ex_doc, "~> 0.30.1", only: :dev, runtime: false}, + {:ex_doc, "~> 0.31.0", only: :dev, runtime: false}, {:number, "~> 1.0.3"} ] end diff --git a/mix.lock b/mix.lock index ba2eb6dab834..f218c4b83a78 100644 --- a/mix.lock +++ b/mix.lock @@ -7,7 +7,7 @@ "b58": {:hex, :b58, "1.0.3", "d300d6ae5a3de956a54b9e8220e924e4fee1a349de983df2340fe61e0e464202", [:mix], [], "hexpm", "af62a98a8661fd89978cf3a3a4b5b2ebe82209de6ac6164f0b112e36af72fc59"}, "bamboo": {:hex, :bamboo, "2.3.0", "d2392a2cabe91edf488553d3c70638b532e8db7b76b84b0a39e3dfe492ffd6fc", [:mix], [{:hackney, ">= 1.15.2", [hex: :hackney, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:mime, "~> 1.4 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "dd0037e68e108fd04d0e8773921512c940e35d981e097b5793543e3b2f9cd3f6"}, "bcrypt_elixir": {:hex, :bcrypt_elixir, "3.1.0", "0b110a9a6c619b19a7f73fa3004aa11d6e719a67e672d1633dc36b6b2290a0f7", [:make, :mix], [{:comeonin, "~> 5.3", [hex: :comeonin, repo: "hexpm", optional: false]}, {:elixir_make, "~> 0.6", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "2ad2acb5a8bc049e8d5aa267802631912bb80d5f4110a178ae7999e69dca1bf7"}, - "benchee": {:hex, :benchee, "1.2.0", "afd2f0caec06ce3a70d9c91c514c0b58114636db9d83c2dc6bfd416656618353", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "ee729e53217898b8fd30aaad3cce61973dab61574ae6f48229fe7ff42d5e4457"}, + "benchee": {:hex, :benchee, "1.3.0", "f64e3b64ad3563fa9838146ddefb2d2f94cf5b473bdfd63f5ca4d0657bf96694", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "34f4294068c11b2bd2ebf2c59aac9c7da26ffa0068afdf3419f1b176e16c5f81"}, "benchee_csv": {:hex, :benchee_csv, "1.0.0", "0b3b9223290bfcb8003552705bec9bcf1a89b4a83b70bd686e45295c264f3d16", [:mix], [{:benchee, ">= 0.99.0 and < 2.0.0", [hex: :benchee, repo: "hexpm", optional: false]}, {:csv, "~> 2.0", [hex: :csv, repo: "hexpm", optional: false]}], "hexpm", "cdefb804c021dcf7a99199492026584be9b5a21d6644ac0d01c81c5d97c520d5"}, "briefly": {:git, "https://github.com/CargoSense/briefly.git", "51dfe7fbe0f897ea2a921d9af120762392aca6a1", []}, "bunt": {:hex, :bunt, "0.2.1", "e2d4792f7bc0ced7583ab54922808919518d0e57ee162901a16a1b6664ef3b14", [:mix], [], "hexpm", "a330bfb4245239787b15005e66ae6845c9cd524a288f0d141c148b02603777a5"}, @@ -37,9 +37,9 @@ "deep_merge": {:hex, :deep_merge, "1.0.0", "b4aa1a0d1acac393bdf38b2291af38cb1d4a52806cf7a4906f718e1feb5ee961", [:mix], [], "hexpm", "ce708e5f094b9cd4e8f2be4f00d2f4250c4095be93f8cd6d018c753894885430"}, "dialyxir": {:hex, :dialyxir, "1.4.1", "a22ed1e7bd3a3e3f197b68d806ef66acb61ee8f57b3ac85fc5d57354c5482a93", [:mix], [{:erlex, ">= 0.2.6", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "84b795d6d7796297cca5a3118444b80c7d94f7ce247d49886e7c291e1ae49801"}, "digital_token": {:hex, :digital_token, "0.6.0", "13e6de581f0b1f6c686f7c7d12ab11a84a7b22fa79adeb4b50eec1a2d278d258", [:mix], [{:cldr_utils, "~> 2.17", [hex: :cldr_utils, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "2455d626e7c61a128b02a4a8caddb092548c3eb613ac6f6a85e4cbb6caddc4d1"}, - "earmark_parser": {:hex, :earmark_parser, "1.4.37", "2ad73550e27c8946648b06905a57e4d454e4d7229c2dafa72a0348c99d8be5f7", [:mix], [], "hexpm", "6b19783f2802f039806f375610faa22da130b8edc21209d0bff47918bb48360e"}, - "ecto": {:hex, :ecto, "3.11.0", "ff8614b4e70a774f9d39af809c426def80852048440e8785d93a6e91f48fec00", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "7769dad267ef967310d6e988e92d772659b11b09a0c015f101ce0fff81ce1f81"}, - "ecto_sql": {:hex, :ecto_sql, "3.11.0", "c787b24b224942b69c9ff7ab9107f258ecdc68326be04815c6cce2941b6fad1c", [:mix], [{:db_connection, "~> 2.5 or ~> 2.4.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.11.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.6.0", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.16.0 or ~> 0.17.0 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:tds, "~> 2.1.1 or ~> 2.2", [hex: :tds, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "77aa3677169f55c2714dda7352d563002d180eb33c0dc29cd36d39c0a1a971f5"}, + "earmark_parser": {:hex, :earmark_parser, "1.4.39", "424642f8335b05bb9eb611aa1564c148a8ee35c9c8a8bba6e129d51a3e3c6769", [:mix], [], "hexpm", "06553a88d1f1846da9ef066b87b57c6f605552cfbe40d20bd8d59cc6bde41944"}, + "ecto": {:hex, :ecto, "3.11.1", "4b4972b717e7ca83d30121b12998f5fcdc62ba0ed4f20fd390f16f3270d85c3e", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "ebd3d3772cd0dfcd8d772659e41ed527c28b2a8bde4b00fe03e0463da0f1983b"}, + "ecto_sql": {:hex, :ecto_sql, "3.11.1", "e9abf28ae27ef3916b43545f9578b4750956ccea444853606472089e7d169470", [:mix], [{:db_connection, "~> 2.5 or ~> 2.4.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.11.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.6.0", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.16.0 or ~> 0.17.0 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:tds, "~> 2.1.1 or ~> 2.2", [hex: :tds, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "ce14063ab3514424276e7e360108ad6c2308f6d88164a076aac8a387e1fea634"}, "elixir_make": {:hex, :elixir_make, "0.7.7", "7128c60c2476019ed978210c245badf08b03dbec4f24d05790ef791da11aa17c", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}], "hexpm", "5bc19fff950fad52bbe5f211b12db9ec82c6b34a9647da0c2224b8b8464c7e6c"}, "erlex": {:hex, :erlex, "0.2.6", "c7987d15e899c7a2f34f5420d2a2ea0d659682c06ac607572df55a43753aa12e", [:mix], [], "hexpm", "2ed2e25711feb44d52b17d2780eabf998452f6efda104877a3881c2f8c0c0c75"}, "ex_abi": {:hex, :ex_abi, "0.6.4", "f722a38298f176dab511cf94627b2815282669255bc2eb834674f23ca71f5cfb", [:mix], [{:ex_keccak, "~> 0.7.3", [hex: :ex_keccak, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "07eaf39b70dd3beac1286c10368d27091a9a64844830eb26a38f1c8d8b19dfbb"}, @@ -48,7 +48,7 @@ "ex_cldr_lists": {:hex, :ex_cldr_lists, "2.10.2", "c8dbb3324ca35cea3679a96f4c774cdf4bdd425786a44c4f52aacb57a0cee446", [:mix], [{:ex_cldr_numbers, "~> 2.25", [hex: :ex_cldr_numbers, repo: "hexpm", optional: false]}, {:ex_doc, "~> 0.18", [hex: :ex_doc, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "0cc2124eccffa5438045c2504dd3365490b64065131f58ecc27f344db1edb4b8"}, "ex_cldr_numbers": {:hex, :ex_cldr_numbers, "2.32.3", "b631ff94c982ec518e46bf4736000a30a33d6b58facc085d5f240305f512ad4a", [:mix], [{:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:digital_token, "~> 0.3 or ~> 1.0", [hex: :digital_token, repo: "hexpm", optional: false]}, {:ex_cldr, "~> 2.37", [hex: :ex_cldr, repo: "hexpm", optional: false]}, {:ex_cldr_currencies, ">= 2.14.2", [hex: :ex_cldr_currencies, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "7b626ff1e59a0ec9c3c5db5ce9ca91a6995e2ab56426b71f3cbf67181ea225f5"}, "ex_cldr_units": {:hex, :ex_cldr_units, "3.16.4", "fee054e9ebed40ef05cbb405cb0c7e7c9fda201f8f03ec0d1e54e879af413246", [:mix], [{:cldr_utils, "~> 2.24", [hex: :cldr_utils, repo: "hexpm", optional: false]}, {:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ex_cldr_lists, "~> 2.10", [hex: :ex_cldr_lists, repo: "hexpm", optional: false]}, {:ex_cldr_numbers, "~> 2.31", [hex: :ex_cldr_numbers, repo: "hexpm", optional: false]}, {:ex_doc, "~> 0.18", [hex: :ex_doc, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "7c15c6357dd555a5bc6c72fdeb243e4706a04065753dbd2f40150f062ca996c7"}, - "ex_doc": {:hex, :ex_doc, "0.30.9", "d691453495c47434c0f2052b08dd91cc32bc4e1a218f86884563448ee2502dd2", [:mix], [{:earmark_parser, "~> 1.4.31", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1", [hex: :makeup_erlang, repo: "hexpm", optional: false]}], "hexpm", "d7aaaf21e95dc5cddabf89063327e96867d00013963eadf2c6ad135506a8bc10"}, + "ex_doc": {:hex, :ex_doc, "0.31.0", "06eb1dfd787445d9cab9a45088405593dd3bb7fe99e097eaa71f37ba80c7a676", [:mix], [{:earmark_parser, "~> 1.4.39", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_c, ">= 0.1.1", [hex: :makeup_c, repo: "hexpm", optional: true]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1", [hex: :makeup_erlang, repo: "hexpm", optional: false]}], "hexpm", "5350cafa6b7f77bdd107aa2199fe277acf29d739aba5aee7e865fc680c62a110"}, "ex_json_schema": {:hex, :ex_json_schema, "0.10.2", "7c4b8c1481fdeb1741e2ce66223976edfb9bccebc8014f6aec35d4efe964fb71", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}], "hexpm", "37f43be60f8407659d4d0155a7e45e7f406dab1f827051d3d35858a709baf6a6"}, "ex_keccak": {:hex, :ex_keccak, "0.7.3", "33298f97159f6b0acd28f6e96ce5ea975a0f4a19f85fe615b4f4579b88b24d06", [:mix], [{:rustler, ">= 0.0.0", [hex: :rustler, repo: "hexpm", optional: true]}, {:rustler_precompiled, "~> 0.6.1", [hex: :rustler_precompiled, repo: "hexpm", optional: false]}], "hexpm", "4c5e6d9d5f77b64ab48769a0166a9814180d40ced68ed74ce60a5174ab55b3fc"}, "ex_machina": {:hex, :ex_machina, "2.7.0", "b792cc3127fd0680fecdb6299235b4727a4944a09ff0fa904cc639272cd92dc7", [:mix], [{:ecto, "~> 2.2 or ~> 3.0", [hex: :ecto, repo: "hexpm", optional: true]}, {:ecto_sql, "~> 3.0", [hex: :ecto_sql, repo: "hexpm", optional: true]}], "hexpm", "419aa7a39bde11894c87a615c4ecaa52d8f107bbdd81d810465186f783245bf8"}, @@ -57,14 +57,14 @@ "ex_utils": {:hex, :ex_utils, "0.1.7", "2c133e0bcdc49a858cf8dacf893308ebc05bc5fba501dc3d2935e65365ec0bf3", [:mix], [], "hexpm", "66d4fe75285948f2d1e69c2a5ddd651c398c813574f8d36a9eef11dc20356ef6"}, "exactor": {:hex, :exactor, "2.2.4", "5efb4ddeb2c48d9a1d7c9b465a6fffdd82300eb9618ece5d34c3334d5d7245b1", [:mix], [], "hexpm", "1222419f706e01bfa1095aec9acf6421367dcfab798a6f67c54cf784733cd6b5"}, "exjsx": {:hex, :exjsx, "4.0.0", "60548841e0212df401e38e63c0078ec57b33e7ea49b032c796ccad8cde794b5c", [:mix], [{:jsx, "~> 2.8.0", [hex: :jsx, repo: "hexpm", optional: false]}], "hexpm", "32e95820a97cffea67830e91514a2ad53b888850442d6d395f53a1ac60c82e07"}, - "expo": {:hex, :expo, "0.4.1", "1c61d18a5df197dfda38861673d392e642649a9cef7694d2f97a587b2cfb319b", [:mix], [], "hexpm", "2ff7ba7a798c8c543c12550fa0e2cbc81b95d4974c65855d8d15ba7b37a1ce47"}, - "exvcr": {:hex, :exvcr, "0.14.4", "1aa5fe7d3f10b117251c158f8d28b39f7fc73d0a7628b2d0b75bf8cfb1111576", [:mix], [{:exactor, "~> 2.2", [hex: :exactor, repo: "hexpm", optional: false]}, {:exjsx, "~> 4.0", [hex: :exjsx, repo: "hexpm", optional: false]}, {:finch, "~> 0.16", [hex: :finch, repo: "hexpm", optional: true]}, {:httpoison, "~> 1.0 or ~> 2.0", [hex: :httpoison, repo: "hexpm", optional: true]}, {:httpotion, "~> 3.1", [hex: :httpotion, repo: "hexpm", optional: true]}, {:ibrowse, "4.4.0", [hex: :ibrowse, repo: "hexpm", optional: true]}, {:meck, "~> 0.8", [hex: :meck, repo: "hexpm", optional: false]}], "hexpm", "4e600568c02ed29d46bc2e2c74927d172ba06658aa8b14705c0207363c44cc94"}, + "expo": {:hex, :expo, "0.5.1", "249e826a897cac48f591deba863b26c16682b43711dd15ee86b92f25eafd96d9", [:mix], [], "hexpm", "68a4233b0658a3d12ee00d27d37d856b1ba48607e7ce20fd376958d0ba6ce92b"}, + "exvcr": {:hex, :exvcr, "0.15.0", "432a4f4b94494f996c96dd2b9b9d3306b70db269ddbdeb9e324a4371f62ce32d", [:mix], [{:exactor, "~> 2.2", [hex: :exactor, repo: "hexpm", optional: false]}, {:exjsx, "~> 4.0", [hex: :exjsx, repo: "hexpm", optional: false]}, {:finch, "~> 0.16", [hex: :finch, repo: "hexpm", optional: true]}, {:httpoison, "~> 1.0 or ~> 2.0", [hex: :httpoison, repo: "hexpm", optional: true]}, {:httpotion, "~> 3.1", [hex: :httpotion, repo: "hexpm", optional: true]}, {:ibrowse, "4.4.0", [hex: :ibrowse, repo: "hexpm", optional: true]}, {:meck, "~> 0.8", [hex: :meck, repo: "hexpm", optional: false]}], "hexpm", "8b7e451f5fd37d1dc1252d08e55291fcb80b55b00cfd84ea41bf64be23cb142c"}, "file_info": {:hex, :file_info, "0.0.4", "2e0e77f211e833f38ead22cb29ce53761d457d80b3ffe0ffe0eb93880b0963b2", [:mix], [{:mimetype_parser, "~> 0.1.2", [hex: :mimetype_parser, repo: "hexpm", optional: false]}], "hexpm", "50e7ad01c2c8b9339010675fe4dc4a113b8d6ca7eddce24d1d74fd0e762781a5"}, "file_system": {:hex, :file_system, "0.2.10", "fb082005a9cd1711c05b5248710f8826b02d7d1784e7c3451f9c1231d4fc162d", [:mix], [], "hexpm", "41195edbfb562a593726eda3b3e8b103a309b733ad25f3d642ba49696bf715dc"}, "floki": {:hex, :floki, "0.35.2", "87f8c75ed8654b9635b311774308b2760b47e9a579dabf2e4d5f1e1d42c39e0b", [:mix], [], "hexpm", "6b05289a8e9eac475f644f09c2e4ba7e19201fd002b89c28c1293e7bd16773d9"}, "flow": {:hex, :flow, "1.2.4", "1dd58918287eb286656008777cb32714b5123d3855956f29aa141ebae456922d", [:mix], [{:gen_stage, "~> 1.0", [hex: :gen_stage, repo: "hexpm", optional: false]}], "hexpm", "874adde96368e71870f3510b91e35bc31652291858c86c0e75359cbdd35eb211"}, "gen_stage": {:hex, :gen_stage, "1.2.1", "19d8b5e9a5996d813b8245338a28246307fd8b9c99d1237de199d21efc4c76a1", [:mix], [], "hexpm", "83e8be657fa05b992ffa6ac1e3af6d57aa50aace8f691fcf696ff02f8335b001"}, - "gettext": {:hex, :gettext, "0.23.1", "821e619a240e6000db2fc16a574ef68b3bd7fe0167ccc264a81563cc93e67a31", [:mix], [{:expo, "~> 0.4.0", [hex: :expo, repo: "hexpm", optional: false]}], "hexpm", "19d744a36b809d810d610b57c27b934425859d158ebd56561bc41f7eeb8795db"}, + "gettext": {:hex, :gettext, "0.24.0", "6f4d90ac5f3111673cbefc4ebee96fe5f37a114861ab8c7b7d5b30a1108ce6d8", [:mix], [{:expo, "~> 0.5.1", [hex: :expo, repo: "hexpm", optional: false]}], "hexpm", "bdf75cdfcbe9e4622dd18e034b227d77dd17f0f133853a1c73b97b3d6c770e8b"}, "hackney": {:hex, :hackney, "1.20.1", "8d97aec62ddddd757d128bfd1df6c5861093419f8f7a4223823537bad5d064e2", [:rebar3], [{:certifi, "~>2.12.0", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "~>6.1.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "~>1.0.0", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "~>1.1", [hex: :mimerl, repo: "hexpm", optional: false]}, {:parse_trans, "3.4.1", [hex: :parse_trans, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "~>1.1.0", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}, {:unicode_util_compat, "~>0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "fe9094e5f1a2a2c0a7d10918fee36bfec0ec2a979994cff8cfe8058cd9af38e3"}, "hammer": {:hex, :hammer, "6.1.0", "f263e3c3e9946bd410ea0336b2abe0cb6260af4afb3a221e1027540706e76c55", [:make, :mix], [{:poolboy, "~> 1.5", [hex: :poolboy, repo: "hexpm", optional: false]}], "hexpm", "b47e415a562a6d072392deabcd58090d8a41182cf9044cdd6b0d0faaaf68ba57"}, "hammer_backend_redis": {:hex, :hammer_backend_redis, "6.1.2", "eb296bb4924928e24135308b2afc189201fd09411c870c6bbadea444a49b2f2c", [:mix], [{:hammer, "~> 6.0", [hex: :hammer, repo: "hexpm", optional: false]}, {:redix, "~> 1.1", [hex: :redix, repo: "hexpm", optional: false]}], "hexpm", "217ea066278910543a5e9b577d5bf2425419446b94fe76bdd9f255f39feec9fa"}, @@ -77,9 +77,9 @@ "junit_formatter": {:hex, :junit_formatter, "3.3.1", "c729befb848f1b9571f317d2fefa648e9d4869befc4b2980daca7c1edc468e40", [:mix], [], "hexpm", "761fc5be4b4c15d8ba91a6dafde0b2c2ae6db9da7b8832a55b5a1deb524da72b"}, "logger_file_backend": {:hex, :logger_file_backend, "0.0.13", "df07b14970e9ac1f57362985d76e6f24e3e1ab05c248055b7d223976881977c2", [:mix], [], "hexpm", "71a453a7e6e899ae4549fb147b1c6621f4233f8f48f58ca10a64ec67b6c50018"}, "logger_json": {:hex, :logger_json, "5.1.2", "7dde5f6dff814aba033f045a3af9408f5459bac72357dc533276b47045371ecf", [:mix], [{:ecto, "~> 2.1 or ~> 3.0", [hex: :ecto, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:phoenix, ">= 1.5.0", [hex: :phoenix, repo: "hexpm", optional: true]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: true]}], "hexpm", "ed42047e5c57a60d0fa1450aef36bc016d0f9a5e6c0807ebb0c03d8895fb6ebc"}, - "makeup": {:hex, :makeup, "1.1.0", "6b67c8bc2882a6b6a445859952a602afc1a41c2e08379ca057c0f525366fc3ca", [:mix], [{:nimble_parsec, "~> 1.2.2 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "0a45ed501f4a8897f580eabf99a2e5234ea3e75a4373c8a52824f6e873be57a6"}, + "makeup": {:hex, :makeup, "1.1.1", "fa0bc768698053b2b3869fa8a62616501ff9d11a562f3ce39580d60860c3a55e", [:mix], [{:nimble_parsec, "~> 1.2.2 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "5dc62fbdd0de44de194898b6710692490be74baa02d9d108bc29f007783b0b48"}, "makeup_elixir": {:hex, :makeup_elixir, "0.16.1", "cc9e3ca312f1cfeccc572b37a09980287e243648108384b97ff2b76e505c3555", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "e127a341ad1b209bd80f7bd1620a15693a9908ed780c3b763bccf7d200c767c6"}, - "makeup_erlang": {:hex, :makeup_erlang, "0.1.2", "ad87296a092a46e03b7e9b0be7631ddcf64c790fa68a9ef5323b6cbb36affc72", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "f3f5a1ca93ce6e092d92b6d9c049bcda58a3b617a8d888f8e7231c85630e8108"}, + "makeup_erlang": {:hex, :makeup_erlang, "0.1.3", "d684f4bac8690e70b06eb52dad65d26de2eefa44cd19d64a8095e1417df7c8fd", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "b78dc853d2e670ff6390b605d807263bf606da3c82be37f9d7f68635bd886fc9"}, "math": {:hex, :math, "0.7.0", "12af548c3892abf939a2e242216c3e7cbfb65b9b2fe0d872d05c6fb609f8127b", [:mix], [], "hexpm", "7987af97a0c6b58ad9db43eb5252a49fc1dfe1f6d98f17da9282e297f594ebc2"}, "meck": {:hex, :meck, "0.9.2", "85ccbab053f1db86c7ca240e9fc718170ee5bda03810a6292b5306bf31bae5f5", [:rebar3], [], "hexpm", "81344f561357dc40a8344afa53767c32669153355b626ea9fcbc8da6b3045826"}, "memento": {:hex, :memento, "0.3.2", "38cfc8ff9bcb1adff7cbd0f3b78a762636b86dff764729d1c82d0464c539bdd0", [:mix], [], "hexpm", "25cf691a98a0cb70262f4a7543c04bab24648cb2041d937eb64154a8d6f8012b"}, @@ -105,12 +105,12 @@ "phoenix_html": {:hex, :phoenix_html, "3.0.4", "232d41884fe6a9c42d09f48397c175cd6f0d443aaa34c7424da47604201df2e1", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "ce17fd3cf815b2ed874114073e743507704b1f5288bb03c304a77458485efc8b"}, "phoenix_live_reload": {:hex, :phoenix_live_reload, "1.3.3", "3a53772a6118d5679bf50fc1670505a290e32a1d195df9e069d8c53ab040c054", [:mix], [{:file_system, "~> 0.2.1 or ~> 0.3", [hex: :file_system, repo: "hexpm", optional: false]}, {:phoenix, "~> 1.4", [hex: :phoenix, repo: "hexpm", optional: false]}], "hexpm", "766796676e5f558dbae5d1bdb066849673e956005e3730dfd5affd7a6da4abac"}, "phoenix_pubsub": {:hex, :phoenix_pubsub, "2.1.3", "3168d78ba41835aecad272d5e8cd51aa87a7ac9eb836eabc42f6e57538e3731d", [:mix], [], "hexpm", "bba06bc1dcfd8cb086759f0edc94a8ba2bc8896d5331a1e2c2902bf8e36ee502"}, - "plug": {:hex, :plug, "1.15.1", "b7efd81c1a1286f13efb3f769de343236bd8b7d23b4a9f40d3002fc39ad8f74c", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "459497bd94d041d98d948054ec6c0b76feacd28eec38b219ca04c0de13c79d30"}, + "plug": {:hex, :plug, "1.15.2", "94cf1fa375526f30ff8770837cb804798e0045fd97185f0bb9e5fcd858c792a3", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "02731fa0c2dcb03d8d21a1d941bdbbe99c2946c0db098eee31008e04c6283615"}, "plug_cowboy": {:hex, :plug_cowboy, "2.6.1", "9a3bbfceeb65eff5f39dab529e5cd79137ac36e913c02067dba3963a26efe9b2", [:mix], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:cowboy_telemetry, "~> 0.3", [hex: :cowboy_telemetry, repo: "hexpm", optional: false]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "de36e1a21f451a18b790f37765db198075c25875c64834bcc82d90b309eb6613"}, "plug_crypto": {:hex, :plug_crypto, "1.2.5", "918772575e48e81e455818229bf719d4ab4181fcbf7f85b68a35620f78d89ced", [:mix], [], "hexpm", "26549a1d6345e2172eb1c233866756ae44a9609bd33ee6f99147ab3fd87fd842"}, "poison": {:hex, :poison, "4.0.1", "bcb755a16fac91cad79bfe9fc3585bb07b9331e50cfe3420a24bcc2d735709ae", [:mix], [], "hexpm", "ba8836feea4b394bb718a161fc59a288fe0109b5006d6bdf97b6badfcf6f0f25"}, "poolboy": {:hex, :poolboy, "1.5.2", "392b007a1693a64540cead79830443abf5762f5d30cf50bc95cb2c1aaafa006b", [:rebar3], [], "hexpm", "dad79704ce5440f3d5a3681c8590b9dc25d1a561e8f5a9c995281012860901e3"}, - "postgrex": {:hex, :postgrex, "0.17.3", "c92cda8de2033a7585dae8c61b1d420a1a1322421df84da9a82a6764580c503d", [:mix], [{:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "946cf46935a4fdca7a81448be76ba3503cff082df42c6ec1ff16a4bdfbfb098d"}, + "postgrex": {:hex, :postgrex, "0.17.4", "5777781f80f53b7c431a001c8dad83ee167bcebcf3a793e3906efff680ab62b3", [:mix], [{:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "6458f7d5b70652bc81c3ea759f91736c16a31be000f306d3c64bcdfe9a18b3cc"}, "prometheus": {:hex, :prometheus, "4.11.0", "b95f8de8530f541bd95951e18e355a840003672e5eda4788c5fa6183406ba29a", [:mix, :rebar3], [{:quantile_estimator, "~> 0.2.1", [hex: :quantile_estimator, repo: "hexpm", optional: false]}], "hexpm", "719862351aabf4df7079b05dc085d2bbcbe3ac0ac3009e956671b1d5ab88247d"}, "prometheus_ecto": {:hex, :prometheus_ecto, "1.4.3", "3dd4da1812b8e0dbee81ea58bb3b62ed7588f2eae0c9e97e434c46807ff82311", [:mix], [{:ecto, "~> 2.0 or ~> 3.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:prometheus_ex, "~> 1.1 or ~> 2.0 or ~> 3.0", [hex: :prometheus_ex, repo: "hexpm", optional: false]}], "hexpm", "8d66289f77f913b37eda81fd287340c17e61a447549deb28efc254532b2bed82"}, "prometheus_ex": {:git, "https://github.com/lanodan/prometheus.ex", "31f7fbe4b71b79ba27efc2a5085746c4011ceb8f", [branch: "fix/elixir-1.14"]}, @@ -137,7 +137,7 @@ "timex": {:hex, :timex, "3.7.11", "bb95cb4eb1d06e27346325de506bcc6c30f9c6dea40d1ebe390b262fad1862d1", [:mix], [{:combine, "~> 0.10", [hex: :combine, repo: "hexpm", optional: false]}, {:gettext, "~> 0.20", [hex: :gettext, repo: "hexpm", optional: false]}, {:tzdata, "~> 1.1", [hex: :tzdata, repo: "hexpm", optional: false]}], "hexpm", "8b9024f7efbabaf9bd7aa04f65cf8dcd7c9818ca5737677c7b76acbc6a94d1aa"}, "toml": {:hex, :toml, "0.6.2", "38f445df384a17e5d382befe30e3489112a48d3ba4c459e543f748c2f25dd4d1", [:mix], [], "hexpm", "d013e45126d74c0c26a38d31f5e8e9b83ea19fc752470feb9a86071ca5a672fa"}, "tzdata": {:hex, :tzdata, "1.1.1", "20c8043476dfda8504952d00adac41c6eda23912278add38edc140ae0c5bcc46", [:mix], [{:hackney, "~> 1.17", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "a69cec8352eafcd2e198dea28a34113b60fdc6cb57eb5ad65c10292a6ba89787"}, - "ueberauth": {:hex, :ueberauth, "0.10.5", "806adb703df87e55b5615cf365e809f84c20c68aa8c08ff8a416a5a6644c4b02", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "3efd1f31d490a125c7ed453b926f7c31d78b97b8a854c755f5c40064bf3ac9e1"}, + "ueberauth": {:hex, :ueberauth, "0.10.7", "5a31cbe11e7ce5c7484d745dc9e1f11948e89662f8510d03c616de03df581ebd", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "0bccf73e2ffd6337971340832947ba232877aa8122dba4c95be9f729c8987377"}, "ueberauth_auth0": {:hex, :ueberauth_auth0, "2.1.0", "0632d5844049fa2f26823f15e1120aa32f27df6f27ce515a4b04641736594bf4", [:mix], [{:oauth2, "~> 2.0", [hex: :oauth2, repo: "hexpm", optional: false]}, {:ueberauth, "~> 0.7", [hex: :ueberauth, repo: "hexpm", optional: false]}], "hexpm", "8d3b30fa27c95c9e82c30c4afb016251405706d2e9627e603c3c9787fd1314fc"}, "unicode_util_compat": {:hex, :unicode_util_compat, "0.7.0", "bc84380c9ab48177092f43ac89e4dfa2c6d62b40b8bd132b1059ecc7232f9a78", [:rebar3], [], "hexpm", "25eee6d67df61960cf6a794239566599b09e17e668d3700247bc498638152521"}, "wallaby": {:hex, :wallaby, "0.30.6", "7dc4c1213f3b52c4152581d126632bc7e06892336d3a0f582853efeeabd45a71", [:mix], [{:ecto_sql, ">= 3.0.0", [hex: :ecto_sql, repo: "hexpm", optional: true]}, {:httpoison, "~> 0.12 or ~> 1.0 or ~> 2.0", [hex: :httpoison, repo: "hexpm", optional: false]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: false]}, {:phoenix_ecto, ">= 3.0.0", [hex: :phoenix_ecto, repo: "hexpm", optional: true]}, {:web_driver_client, "~> 0.2.0", [hex: :web_driver_client, repo: "hexpm", optional: false]}], "hexpm", "50950c1d968549b54c20e16175c68c7fc0824138e2bb93feb11ef6add8eb23d4"}, diff --git a/rel/config.exs b/rel/config.exs index 86844487f4aa..b7c3f38132d3 100644 --- a/rel/config.exs +++ b/rel/config.exs @@ -71,7 +71,7 @@ end # will be used by default release :blockscout do - set version: "5.3.2-beta" + set version: "6.0.0-beta" set applications: [ :runtime_tools, block_scout_web: :permanent,