Merge pull request #9067 from blockscout/v6.0.0-dev

v6.0.0 DB migrations
pull/9116/head v6.0.0-beta
Victor Baranov 10 months ago committed by GitHub
commit ce735c1294
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 10
      .dialyzer-ignore
  2. 2
      .github/ISSUE_TEMPLATE/bug_report.yml
  3. 26
      .github/workflows/config.yml
  4. 2
      .github/workflows/prerelease.yml
  5. 2
      .github/workflows/publish-docker-image-every-push.yml
  6. 2
      .github/workflows/publish-docker-image-for-core.yml
  7. 2
      .github/workflows/publish-docker-image-for-eth-goerli.yml
  8. 2
      .github/workflows/publish-docker-image-for-eth-sepolia.yml
  9. 2
      .github/workflows/publish-docker-image-for-eth.yml
  10. 2
      .github/workflows/publish-docker-image-for-fuse.yml
  11. 2
      .github/workflows/publish-docker-image-for-immutable.yml
  12. 2
      .github/workflows/publish-docker-image-for-l2-staging.yml
  13. 2
      .github/workflows/publish-docker-image-for-lukso.yml
  14. 2
      .github/workflows/publish-docker-image-for-optimism.yml
  15. 2
      .github/workflows/publish-docker-image-for-polygon-edge.yml
  16. 2
      .github/workflows/publish-docker-image-for-rsk.yml
  17. 2
      .github/workflows/publish-docker-image-for-stability.yml
  18. 2
      .github/workflows/publish-docker-image-for-suave.yml
  19. 2
      .github/workflows/publish-docker-image-for-xdai.yml
  20. 2
      .github/workflows/publish-docker-image-for-zkevm.yml
  21. 2
      .github/workflows/publish-docker-image-for-zksync.yml
  22. 2
      .github/workflows/publish-docker-image-staging-on-demand.yml
  23. 2
      .github/workflows/release-additional.yml
  24. 2
      .github/workflows/release.yml
  25. 37
      CHANGELOG.md
  26. 6
      apps/block_scout_web/lib/block_scout_web/controllers/address_token_transfer_controller.ex
  27. 4
      apps/block_scout_web/lib/block_scout_web/controllers/address_transaction_controller.ex
  28. 4
      apps/block_scout_web/lib/block_scout_web/controllers/api/rpc/transaction_controller.ex
  29. 1
      apps/block_scout_web/lib/block_scout_web/controllers/api/v2/transaction_controller.ex
  30. 29
      apps/block_scout_web/lib/block_scout_web/controllers/recent_transactions_controller.ex
  31. 13
      apps/block_scout_web/lib/block_scout_web/controllers/transaction_controller.ex
  32. 28
      apps/block_scout_web/lib/block_scout_web/controllers/transaction_internal_transaction_controller.ex
  33. 2
      apps/block_scout_web/lib/block_scout_web/models/transaction_state_helper.ex
  34. 6
      apps/block_scout_web/lib/block_scout_web/notifier.ex
  35. 1
      apps/block_scout_web/lib/block_scout_web/schema/types.ex
  36. 2
      apps/block_scout_web/lib/block_scout_web/templates/block/_link.html.eex
  37. 2
      apps/block_scout_web/lib/block_scout_web/templates/tokens/transfer/_token_transfer.html.eex
  38. 2
      apps/block_scout_web/lib/block_scout_web/views/api/rpc/logs_view.ex
  39. 3
      apps/block_scout_web/lib/block_scout_web/views/api/rpc/transaction_view.ex
  40. 3
      apps/block_scout_web/lib/block_scout_web/views/api/v2/transaction_view.ex
  41. 8
      apps/block_scout_web/lib/block_scout_web/views/transaction_view.ex
  42. 2
      apps/block_scout_web/mix.exs
  43. 48
      apps/block_scout_web/priv/gettext/default.pot
  44. 48
      apps/block_scout_web/priv/gettext/en/LC_MESSAGES/default.po
  45. 37
      apps/block_scout_web/test/block_scout_web/channels/websocket_v2_test.exs
  46. 6
      apps/block_scout_web/test/block_scout_web/controllers/api/rpc/address_controller_test.exs
  47. 161
      apps/block_scout_web/test/block_scout_web/controllers/api/rpc/eth_controller_test.exs
  48. 87
      apps/block_scout_web/test/block_scout_web/controllers/api/rpc/logs_controller_test.exs
  49. 18
      apps/block_scout_web/test/block_scout_web/controllers/api/rpc/transaction_controller_test.exs
  50. 10
      apps/block_scout_web/test/block_scout_web/controllers/api/v2/address_controller_test.exs
  51. 2
      apps/block_scout_web/test/block_scout_web/controllers/api/v2/search_controller_test.exs
  52. 11
      apps/block_scout_web/test/block_scout_web/controllers/api/v2/transaction_controller_test.exs
  53. 2
      apps/block_scout_web/test/block_scout_web/controllers/tokens/inventory_controller_test.exs
  54. 3
      apps/block_scout_web/test/block_scout_web/schema/query/token_transfers_test.exs
  55. 2
      apps/block_scout_web/test/block_scout_web/views/tokens/helper_test.exs
  56. 4
      apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/block.ex
  57. 43
      apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/log.ex
  58. 15
      apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/receipts.ex
  59. 75
      apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/transaction.ex
  60. 4
      apps/ethereum_jsonrpc/lib/ethereum_jsonrpc/transactions.ex
  61. 2
      apps/ethereum_jsonrpc/mix.exs
  62. 6
      apps/ethereum_jsonrpc/test/ethereum_jsonrpc/receipts_test.exs
  63. 6
      apps/explorer/config/config.exs
  64. 6
      apps/explorer/config/runtime/test.exs
  65. 10
      apps/explorer/lib/explorer/application.ex
  66. 93
      apps/explorer/lib/explorer/chain.ex
  67. 29
      apps/explorer/lib/explorer/chain/address/token_balance.ex
  68. 45
      apps/explorer/lib/explorer/chain/cache/background_migrations.ex
  69. 217
      apps/explorer/lib/explorer/chain/cache/gas_price_oracle.ex
  70. 6
      apps/explorer/lib/explorer/chain/csv_export/address_token_transfer_csv_exporter.ex
  71. 17
      apps/explorer/lib/explorer/chain/csv_export/address_transaction_csv_exporter.ex
  72. 50
      apps/explorer/lib/explorer/chain/denormalization_helper.ex
  73. 12
      apps/explorer/lib/explorer/chain/import/runner/blocks.ex
  74. 27
      apps/explorer/lib/explorer/chain/import/runner/internal_transactions.ex
  75. 6
      apps/explorer/lib/explorer/chain/import/runner/logs.ex
  76. 47
      apps/explorer/lib/explorer/chain/import/runner/transactions.ex
  77. 48
      apps/explorer/lib/explorer/chain/log.ex
  78. 95
      apps/explorer/lib/explorer/chain/search.ex
  79. 40
      apps/explorer/lib/explorer/chain/token_transfer.ex
  80. 22
      apps/explorer/lib/explorer/chain/transaction.ex
  81. 52
      apps/explorer/lib/explorer/chain/transaction/history/historian.ex
  82. 2
      apps/explorer/lib/explorer/chain/transaction/state_change.ex
  83. 7
      apps/explorer/lib/explorer/eth_rpc.ex
  84. 276
      apps/explorer/lib/explorer/etherscan.ex
  85. 307
      apps/explorer/lib/explorer/etherscan/logs.ex
  86. 51
      apps/explorer/lib/explorer/migrator/address_current_token_balance_token_type.ex
  87. 51
      apps/explorer/lib/explorer/migrator/address_token_balance_token_type.ex
  88. 84
      apps/explorer/lib/explorer/migrator/filling_migration.ex
  89. 32
      apps/explorer/lib/explorer/migrator/migration_status.ex
  90. 53
      apps/explorer/lib/explorer/migrator/transactions_denormalization.ex
  91. 65
      apps/explorer/lib/explorer/token_transfer_token_id_migration/lowest_block_number_updater.ex
  92. 70
      apps/explorer/lib/explorer/token_transfer_token_id_migration/supervisor.ex
  93. 84
      apps/explorer/lib/explorer/token_transfer_token_id_migration/worker.ex
  94. 67
      apps/explorer/lib/explorer/utility/token_transfer_token_id_migrator_progress.ex
  95. 2
      apps/explorer/mix.exs
  96. 13
      apps/explorer/priv/repo/migrations/20231212101547_add_block_timestamp_and_consensus_to_transactions.exs
  97. 12
      apps/explorer/priv/repo/migrations/20231212102127_create_migrations_status.exs
  98. 11
      apps/explorer/priv/repo/migrations/20231213085254_add_btree_gin_extension.exs
  99. 26
      apps/explorer/priv/repo/migrations/20231213090140_add_token_transfers_token_contract_address_token_ids_index.exs
  100. 7
      apps/explorer/priv/repo/migrations/20231213101235_drop_token_transfers_token_ids_index.exs
  101. Some files were not shown because too many files have changed in this diff Show More

@ -14,13 +14,13 @@ lib/phoenix/router.ex:324
lib/phoenix/router.ex:402
lib/explorer/smart_contract/reader.ex:435
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

@ -65,7 +65,7 @@ body:
attributes:
label: Backend version
description: The release version of the backend or branch/commit.
placeholder: v5.4.0
placeholder: v6.0.0
validations:
required: true

@ -75,7 +75,7 @@ jobs:
path: |
deps
_build
key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_32-${{ 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-
@ -133,7 +133,7 @@ jobs:
path: |
deps
_build
key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_32-${{ 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-"
@ -157,7 +157,7 @@ jobs:
path: |
deps
_build
key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_32-${{ 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-"
@ -186,7 +186,7 @@ jobs:
path: |
deps
_build
key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_32-${{ 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-"
@ -230,7 +230,7 @@ jobs:
path: |
deps
_build
key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_32-${{ 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-"
@ -256,7 +256,7 @@ jobs:
path: |
deps
_build
key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_32-${{ 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-"
@ -285,7 +285,7 @@ jobs:
path: |
deps
_build
key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_32-${{ 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-"
@ -333,7 +333,7 @@ jobs:
path: |
deps
_build
key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_32-${{ 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-"
@ -379,7 +379,7 @@ jobs:
path: |
deps
_build
key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_32-${{ 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-"
@ -441,7 +441,7 @@ jobs:
path: |
deps
_build
key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_32-${{ 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-"
@ -501,7 +501,7 @@ jobs:
path: |
deps
_build
key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_32-${{ 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-"
@ -572,7 +572,7 @@ jobs:
path: |
deps
_build
key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_32-${{ 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-"
@ -640,7 +640,7 @@ jobs:
path: |
deps
_build
key: ${{ runner.os }}-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ env.MIX_ENV }}-deps-mixlockhash_32-${{ 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-"

@ -16,7 +16,7 @@ jobs:
name: Push Docker image to Docker Hub
runs-on: ubuntu-latest
env:
RELEASE_VERSION: 5.4.0
RELEASE_VERSION: 6.0.0
steps:
- name: Check out the repo
uses: actions/checkout@v4

@ -11,7 +11,7 @@ on:
env:
OTP_VERSION: '25.2.1'
ELIXIR_VERSION: '1.14.5'
RELEASE_VERSION: 5.4.0
RELEASE_VERSION: 6.0.0
jobs:
push_to_registry:

@ -15,7 +15,7 @@ jobs:
name: Push Docker image to Docker Hub
runs-on: ubuntu-latest
env:
RELEASE_VERSION: 5.4.0
RELEASE_VERSION: 6.0.0
DOCKER_CHAIN_NAME: poa
steps:
- name: Check out the repo

@ -15,7 +15,7 @@ jobs:
name: Push Docker image to Docker Hub
runs-on: ubuntu-latest
env:
RELEASE_VERSION: 5.4.0
RELEASE_VERSION: 6.0.0
DOCKER_CHAIN_NAME: eth-goerli
steps:
- name: Check out the repo

@ -15,7 +15,7 @@ jobs:
name: Push Docker image to Docker Hub
runs-on: ubuntu-latest
env:
RELEASE_VERSION: 5.4.0
RELEASE_VERSION: 6.0.0
DOCKER_CHAIN_NAME: eth-sepolia
steps:
- name: Check out the repo

@ -15,7 +15,7 @@ jobs:
name: Push Docker image to Docker Hub
runs-on: ubuntu-latest
env:
RELEASE_VERSION: 5.4.0
RELEASE_VERSION: 6.0.0
DOCKER_CHAIN_NAME: mainnet
steps:
- name: Check out the repo

@ -15,7 +15,7 @@ jobs:
name: Push Docker image to Docker Hub
runs-on: ubuntu-latest
env:
RELEASE_VERSION: 5.4.0
RELEASE_VERSION: 6.0.0
DOCKER_CHAIN_NAME: fuse
steps:
- name: Check out the repo

@ -15,7 +15,7 @@ jobs:
name: Push Docker image to Docker Hub
runs-on: ubuntu-latest
env:
RELEASE_VERSION: 5.4.0
RELEASE_VERSION: 6.0.0
DOCKER_CHAIN_NAME: immutable
steps:
- name: Check out the repo

@ -15,7 +15,7 @@ jobs:
name: Push Docker image to Docker Hub
runs-on: ubuntu-latest
env:
RELEASE_VERSION: 5.4.0
RELEASE_VERSION: 6.0.0
DOCKER_CHAIN_NAME: optimism-l2-advanced
steps:
- name: Check out the repo

@ -15,7 +15,7 @@ jobs:
name: Push Docker image to Docker Hub
runs-on: ubuntu-latest
env:
RELEASE_VERSION: 5.4.0
RELEASE_VERSION: 6.0.0
DOCKER_CHAIN_NAME: lukso
steps:
- name: Check out the repo

@ -15,7 +15,7 @@ jobs:
name: Push Docker image to Docker Hub
runs-on: ubuntu-latest
env:
RELEASE_VERSION: 5.4.0
RELEASE_VERSION: 6.0.0
DOCKER_CHAIN_NAME: optimism
steps:
- name: Check out the repo

@ -15,7 +15,7 @@ jobs:
name: Push Docker image to Docker Hub
runs-on: ubuntu-latest
env:
RELEASE_VERSION: 5.4.0
RELEASE_VERSION: 6.0.0
DOCKER_CHAIN_NAME: polygon-edge
steps:
- name: Check out the repo

@ -15,7 +15,7 @@ jobs:
name: Push Docker image to Docker Hub
runs-on: ubuntu-latest
env:
RELEASE_VERSION: 5.4.0
RELEASE_VERSION: 6.0.0
DOCKER_CHAIN_NAME: rsk
steps:
- name: Check out the repo

@ -18,7 +18,7 @@ jobs:
name: Push Docker image to Docker Hub
runs-on: ubuntu-latest
env:
RELEASE_VERSION: 5.4.0
RELEASE_VERSION: 6.0.0
DOCKER_CHAIN_NAME: stability
steps:
- name: Check out the repo

@ -18,7 +18,7 @@ jobs:
name: Push Docker image to Docker Hub
runs-on: ubuntu-latest
env:
RELEASE_VERSION: 5.4.0
RELEASE_VERSION: 6.0.0
DOCKER_CHAIN_NAME: suave
steps:
- name: Check out the repo

@ -15,7 +15,7 @@ jobs:
name: Push Docker image to Docker Hub
runs-on: ubuntu-latest
env:
RELEASE_VERSION: 5.4.0
RELEASE_VERSION: 6.0.0
DOCKER_CHAIN_NAME: xdai
steps:
- name: Check out the repo

@ -15,7 +15,7 @@ jobs:
name: Push Docker image to Docker Hub
runs-on: ubuntu-latest
env:
RELEASE_VERSION: 5.4.0
RELEASE_VERSION: 6.0.0
DOCKER_CHAIN_NAME: zkevm
steps:
- name: Check out the repo

@ -15,7 +15,7 @@ jobs:
name: Push Docker image to Docker Hub
runs-on: ubuntu-latest
env:
RELEASE_VERSION: 5.4.0
RELEASE_VERSION: 6.0.0
DOCKER_CHAIN_NAME: zksync
steps:
- name: Check out the repo

@ -12,7 +12,7 @@ on:
env:
OTP_VERSION: '25.2.1'
ELIXIR_VERSION: '1.14.5'
RELEASE_VERSION: 5.4.0
RELEASE_VERSION: 6.0.0
jobs:
push_to_registry:

@ -18,7 +18,7 @@ jobs:
name: Push Docker image to Docker Hub
runs-on: ubuntu-latest
env:
RELEASE_VERSION: 5.4.0
RELEASE_VERSION: 6.0.0
steps:
- name: Check out the repo
uses: actions/checkout@v4

@ -18,7 +18,7 @@ jobs:
name: Push Docker image to Docker Hub
runs-on: ubuntu-latest
env:
RELEASE_VERSION: 5.4.0
RELEASE_VERSION: 6.0.0
steps:
- name: Check out the repo
uses: actions/checkout@v4

@ -4,20 +4,57 @@
### Features
### Fixes
### Chore
<details>
<summary>Dependencies version bumps</summary>
</details>
## 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
<details>
<summary>Dependencies version bumps</summary>
- [#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
</details>
## 5.4.0-beta

@ -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))

@ -20,7 +20,7 @@ defmodule BlockScoutWeb.AddressTransactionController do
AddressTransactionCsvExporter
}
alias Explorer.Chain.{Transaction, 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,6 +49,7 @@ 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))

@ -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

@ -62,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

@ -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 =

@ -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(

@ -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)

@ -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

@ -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)

@ -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)

@ -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)
) %>

@ -44,7 +44,7 @@
to: block_path(BlockScoutWeb.Endpoint, :show, @token_transfer.block_number)
) %>
</span>
<span class="mr-2 mr-md-0 order-2" data-from-now="<%= @token_transfer.transaction.block && @token_transfer.transaction.block.timestamp %>"></span>
<span class="mr-2 mr-md-0 order-2" data-from-now="<%= BlockScoutWeb.TransactionView.block_timestamp(@token_transfer.transaction) %>"></span>
</div>
</div>
</div>

@ -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()

@ -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),

@ -375,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,
@ -833,6 +833,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

@ -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

@ -23,7 +23,7 @@ defmodule BlockScoutWeb.Mixfile do
dialyzer: :test
],
start_permanent: Mix.env() == :prod,
version: "5.4.0",
version: "6.0.0",
xref: [exclude: [Explorer.Chain.Zkevm.Reader]]
]
end

@ -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 ""
@ -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 ""
@ -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 ""
@ -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 ""
@ -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 ""
@ -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 ""
@ -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 ""

@ -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 ""
@ -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 ""
@ -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 ""
@ -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 ""
@ -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 ""
@ -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 ""
@ -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 ""

@ -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,

@ -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

@ -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})

@ -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))

@ -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}"
}
],

@ -26,12 +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)
@ -1761,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

@ -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)

@ -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"
)

@ -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"})

@ -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

@ -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

@ -788,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

@ -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

@ -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",

@ -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
@ -658,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

@ -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

@ -23,7 +23,7 @@ defmodule EthereumJsonrpc.MixProject do
dialyzer: :test
],
start_permanent: Mix.env() == :prod,
version: "5.4.0"
version: "6.0.0"
]
end

@ -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,

@ -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

@ -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

@ -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,
@ -120,12 +122,14 @@ defmodule Explorer.Application do
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()

@ -51,6 +51,7 @@ defmodule Explorer.Chain do
CurrencyHelper,
Data,
DecompiledSmartContract,
DenormalizationHelper,
Hash,
Import,
InternalTransaction,
@ -352,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
@ -521,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
@ -3561,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 ->

@ -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 """

@ -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

@ -14,6 +14,8 @@ defmodule Explorer.Chain.Cache.GasPriceOracle do
alias Explorer.Chain.{
Block,
DenormalizationHelper,
Transaction,
Wei
}
@ -73,77 +75,150 @@ defmodule Explorer.Chain.Cache.GasPriceOracle do
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,
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
)
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)

@ -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}
@ -68,7 +68,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),
@ -118,7 +118,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

@ -10,15 +10,9 @@ defmodule Explorer.Chain.CSVExport.AddressTransactionCsvExporter do
alias Explorer.{Chain, Market, PagingOptions, Repo}
alias Explorer.Market.MarketHistory
alias Explorer.Chain.{Address, Hash, 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(Hash.Address.t(), String.t(), String.t(), String.t() | nil, String.t() | nil) :: Enumerable.t()
@ -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)
@ -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),

@ -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

@ -395,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))

@ -693,7 +693,7 @@ defmodule Explorer.Chain.Import.Runner.InternalTransactions do
defp remove_consensus_of_invalid_blocks(repo, invalid_block_numbers) do
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,
@ -703,8 +703,18 @@ defmodule Explorer.Chain.Import.Runner.InternalTransactions do
update: [set: [consensus: false]]
)
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)
@ -762,4 +772,17 @@ defmodule Explorer.Chain.Import.Runner.InternalTransactions do
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

@ -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

@ -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

@ -12,7 +12,7 @@ defmodule Explorer.Chain.Log do
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
@ -190,9 +183,10 @@ defmodule Explorer.Chain.Log do
end
defp find_method_candidates(log, transaction, options, events_acc, skip_sig_provider?) do
with "0x" <> hex_part <- log.first_topic,
{number, ""} <- Integer.parse(hex_part, 16) do
<<method_id::binary-size(4), _rest::binary>> = :binary.encode_unsigned(number)
if is_nil(log.first_topic) do
{{:error, :could_not_decode}, events_acc}
else
<<method_id::binary-size(4), _rest::binary>> = log.first_topic.bytes
if Map.has_key?(events_acc, method_id) do
{events_acc[method_id], events_acc}
@ -200,8 +194,6 @@ defmodule Explorer.Chain.Log do
result = find_method_candidates_from_db(method_id, log, transaction, options, skip_sig_provider?)
{result, Map.put(events_acc, method_id, result)}
end
else
_ -> {{:error, :could_not_decode}, events_acc}
end
end
@ -243,10 +235,10 @@ defmodule Explorer.Chain.Log do
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}

@ -21,6 +21,7 @@ defmodule Explorer.Chain.Search do
alias Explorer.Chain.{
Address,
Block,
DenormalizationHelper,
SmartContract,
Token,
Transaction
@ -382,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

@ -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

@ -17,6 +17,7 @@ defmodule Explorer.Chain.Transaction do
Block,
ContractMethod,
Data,
DenormalizationHelper,
Gas,
Hash,
InternalTransaction,
@ -36,7 +37,7 @@ defmodule Explorer.Chain.Transaction do
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)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,
@ -232,6 +237,7 @@ defmodule Explorer.Chain.Transaction do
@derive {Poison.Encoder,
only: [
:block_number,
:block_timestamp,
:cumulative_gas_used,
:error,
:gas,
@ -252,6 +258,7 @@ defmodule Explorer.Chain.Transaction do
@derive {Jason.Encoder,
only: [
:block_number,
:block_timestamp,
:cumulative_gas_used,
:error,
:gas,
@ -272,6 +279,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)
@ -544,6 +553,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(
@ -1019,11 +1033,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
@ -1040,11 +1055,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

@ -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}")

@ -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 =

@ -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

@ -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

@ -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 [

@ -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

@ -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

@ -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

@ -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

@ -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

@ -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

@ -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

@ -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

@ -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

@ -24,7 +24,7 @@ defmodule Explorer.Mixfile do
dialyzer: :test
],
start_permanent: Mix.env() == :prod,
version: "5.4.0",
version: "6.0.0",
xref: [exclude: [BlockScoutWeb.WebRouter.Helpers]]
]
end

@ -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

@ -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

@ -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

@ -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

@ -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

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save